CALayer是什么
- 在iOS中,你能看得见摸得着的东西基本上都是UIView,比如一个按钮、一个文本标签、一个文本输入框、一个图标等等,这些都是UIView ;其实UIView之所以能显示在屏幕上,完全是因为它内部的一个图层
- 在创建UIView对象时,UIView内部会自动创建一个图层(即CALayer对象),通过UIView的layer属性可以访问这个层
@property(nonatomic,readonly,retain) CALayer *layer; - 当UIView需要显示到屏幕上时,会调用drawRect:方法进行绘图,并且会将所有内容绘制在自己的图层上,绘图完毕后,系统会将图层拷贝到屏幕上,于是就完成了UIView的显示
- 换句话说,UIView本身不具备显示的功能,是它内部的层才有显示功能
- CALayer是定义在QuartzCore框架中的(Core Animation);CGImageRef、CGColorRef两种数据类型是定义在CoreGraphics框架中的;UIColor、UIImage是定义在UIKit框架中的
UIView和CALayer的选择
- 通过CALayer,就能做出跟UIView一样的界面效果;既然CALayer和UIView都能实现相同的显示效果,那究竟该选择谁好呢?
- 其实,对比CALayer,UIView多了一个事件处理的功能。也就是说,CALayer不能处理用户的触摸事件,而UIView可以
- 所以,如果显示出来的东西需要跟用户进行交互的话,用UIView;如果不需要跟用户进行交互,用UIView或者CALayer都可以;当然,CALayer的性能会高一些,因为它少了事件处理的功能,更加轻量级
图层的添加
1️⃣添加一个简单的图层
1 CALayer *myLayer = [CALayer layer];
2 // 设置层的宽度和高度(100x100)
3 myLayer.bounds = CGRectMake(0, 0, 100, 100);
4 // 设置层的位置
5 myLayer.position = CGPointMake(100, 100);
6 // 设置层的背景颜色:红色
7 myLayer.backgroundColor = [UIColor redColor].CGColor;
8 // 设置层的圆角半径为10
9 myLayer.cornerRadius = 10;
10
11 // 添加myLayer到控制器的view的layer中
12 [self.view.layer addSublayer:myLayer];
2️⃣添加一个显示图片的图层
1 CALayer *myLayer = [CALayer layer];
2 // 设置层的宽度和高度(100x100)
3 myLayer.bounds = CGRectMake(0, 0, 100, 100);
4 // 设置层的位置
5 myLayer.position = CGPointMake(100, 100);
6 // 设置需要显示的图片
7 myLayer.contents = (id)[UIImage imageNamed:@"lufy.png"].CGImage;
8 // 设置层的圆角半径为10
9 myLayer.cornerRadius = 10;
10 // 如果设置了图片,需要设置这个属性为YES才有圆角效果
11 myLayer.masksToBounds = YES;
12
13 // 添加myLayer到控制器的view的layer中
14 [self.view.layer addSublayer:myLayer];
3️⃣为什么CALayer中使用CGColorRef和CGImageRef这2种数据类型,而不用UIColor和UIImage?
- 首先要知道:CALayer是定义在QuartzCore框架中的;CGImageRef、CGColorRef两种数据类型是定义在CoreGraphics框架中的;UIColor、UIImage是定义在UIKit框架中的
- 其次,QuartzCore框架和CoreGraphics框架是可以跨平台使用的,在iOS和Mac OS X上都能使用,但是UIKit只能在iOS中使用
- 因此,为了保证可移植性,QuartzCore不能使用UIImage、UIColor,只能使用CGImageRef、CGColorRef
- 不过很多情况下,可以通过UIKit对象的特定方法,得到CoreGraphics对象,比如UIImage的CGImage方法可以返回一个CGImageRef
UIView和CALayer的其他关系
- UIView可以通过subviews属性访问所有的子视图,类似地,CALayer也可以通过sublayers属性访问所有的子层
- UIView可以通过superview属性访问父视图,类似地,CALayer也可以通过superlayer属性访问父层
-
下面再看一张UIView和CALayer的关系图:
如果两个UIView是父子关系,那么它们内部的CALayer也是父子关系。
CALayer中的position和anchorPoint属性
- position和anchorPoint属性都是CGPoint类型的
- position可以用来设置CALayer在父层中的位置,它是以父层的左上角为坐标原点(0, 0)
- anchorPoint称为"定位点",它决定着CALayer身上的哪个点会在position属性所指的位置。它的x、y取值范围都是0~1,默认值为(0.5, 0.5)
1.创建一个CALayer,添加到控制器的view的layer中
1 CALayer *myLayer = [CALayer layer];
2 // 设置层的宽度和高度(100x100)
3 myLayer.bounds = CGRectMake(0, 0, 100, 100);
4 // 设置层的位置
5 myLayer.position = CGPointMake(100, 100);
6 // 设置层的背景颜色:红色
7 myLayer.backgroundColor = [UIColor redColor].CGColor;
8
9 // 添加myLayer到控制器的view的layer中
10 [self.view.layer addSublayer:myLayer];
第5行设置了myLayer的position为(100, 100),又因为anchorPoint默认是(0.5, 0.5),所以最后的效果是:myLayer的中点会在父层的(100, 100)位置
2.若将anchorPoint改为(0, 0),myLayer的左上角会在(100, 100)位置
3.若将anchorPoint改为(1, 1),myLayer的右下角会在(100, 100)位置
4.将anchorPoint改为(0, 1),myLayer的左下角会在(100, 100)位置
总结anchorPoint的用途:它决定着CALayer身上的哪个点会在position所指定的位置上。它的x、y取值范围都是0~1,默认值为(0.5, 0.5),因此,默认情况下,CALayer的中点会在position所指定的位置上。当anchorPoint为其他值时,以此类推。
自定义层
方法1
1️⃣创建一个CALayer的子类
#import<QuartzCore/QuartzCore.h>
@interface MJLayer:CALayer
@end
2️⃣在.m文件中覆盖drawInContext:方法,在里面绘图
1 @implementation MJLayer
2
3 #pragma mark 绘制一个实心三角形
4 - (void)drawInContext:(CGContextRef)ctx {
5 // 设置为蓝色
6 CGContextSetRGBFillColor(ctx, 0, 0, 1, 1);
7
8
9 // 设置起点
10 CGContextMoveToPoint(ctx, 50, 0);
11 // 从(50, 0)连线到(0, 100)
12 CGContextAddLineToPoint(ctx, 0, 100);
13 // 从(0, 100)连线到(100, 100)
14 CGContextAddLineToPoint(ctx, 100, 100);
15 // 合并路径,连接起点和终点
16 CGContextClosePath(ctx);
17
18 // 绘制路径
19 CGContextFillPath(ctx);
20 }
21
22 @end
3️⃣在控制器中添加图层到屏幕上
1 MJLayer *layer = [MJLayer layer];
2 // 设置层的宽高
3 layer.bounds = CGRectMake(0, 0, 100, 100);
4 // 设置层的位置
5 layer.position = CGPointMake(100, 100);
6 // 开始绘制图层
7 [layer setNeedsDisplay];
8 [self.view.layer addSublayer:layer];
注意第7行,需要调用setNeedsDisplay这个方法,才会触发drawInContext:方法的调用,然后进行绘图
方法2
方法描述:设置CALayer的delegate,然后让delegate实现drawLayer:inContext:方法,当CALayer需要绘图时,会调用delegate的drawLayer:inContext:方法进行绘图。
- 这里要注意的是:不能再将某个UIView设置为CALayer的delegate,因为UIView对象已经是它内部根层的delegate,再次设置为其他层的delegate就会出问题。UIView和它内部CALayer的默认关系图:
1️⃣创建新的层,设置delegate,然后添加到控制器的view的layer中
1 CALayer *layer = [CALayer layer];
2 // 设置delegate
3 layer.delegate = self;
4 // 设置层的宽高
5 layer.bounds = CGRectMake(0, 0, 100, 100);
6 // 设置层的位置
7 layer.position = CGPointMake(100, 100);
8 // 开始绘制图层
9 [layer setNeedsDisplay];
10 [self.view.layer addSublayer:layer];
- 在第3行设置了CALayer的delegate,这里的self是指控制器
- 注意第9行,需要调用setNeedsDisplay这个方法,才会通知delegate进行绘图
2️⃣让CALayer的delegate(前面设置的是控制器)实现drawLayer:inContext:方法
1 #pragma mark 画一个矩形框
2 - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
3 // 设置蓝色
4 CGContextSetRGBStrokeColor(ctx, 0, 0, 1, 1);
5 // 设置边框宽度
6 CGContextSetLineWidth(ctx, 10);
7
8 // 添加一个跟层一样大的矩形到路径中
9 CGContextAddRect(ctx, layer.bounds);
10
11 // 绘制路径
12 CGContextStrokePath(ctx);
13 }
总结
1️⃣无论采取哪种方法来自定义层,都必须调用CALayer的setNeedsDisplay方法才能正常绘图。
2️⃣UIView的详细显示过程
- 当UIView需要显示时,它内部的层会准备好一个CGContextRef(图形上下文),然后调用delegate(这里就是UIView)的drawLayer:inContext:方法,并且传入已经准备好的CGContextRef对象。而UIView在drawLayer:inContext:方法中又会调用自己的drawRect:方法
- 平时在drawRect:中通过UIGraphicsGetCurrentContext()获取的就是由层传入的CGContextRef对象,在drawRect:中完成的所有绘图都会填入层的CGContextRef中,然后被拷贝至屏幕