##1、CALayer概述
CALayer
类在概念上和UIView
类似,同样也是一些被层级关系 树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置。它们有一些⽅法和属性用来做动画和变换。 CALayer是CoreAnimation部分的内容,CALayer的概念类似于photoshop中层的概念,每个UIView都有一个根CALayer,每个CALayer又可以添加子CALayer,从结构上来看CALayer是一种树形结构,UIView的绘制工作都交由CALayer完成。
和UIView最大的不同是CALayer
不处理⽤用户的交互。因为CALayer
并不清楚具体的响应链(iOS通过视图层级关系用来传送触摸事件的机制),于是它并不能够响应事件,即使它提供了一些方法来判断是否一个触点在图层的范围之内。
##2、平行的层级关系 每一个UIview
都有一个CALayer
实例的图层属性,也就是所谓的backing layer,视图的职责就是创建并管理这个图层,以确保当子视图在层级关系中添加或者被移除的时候,他们关联的图层也同样对应在层级关系树当中有相同的操作。 实际上,这些背后关联的图层才是真正用来在屏幕上显示和做画,UIView
仅仅是对CALayer
的一个封装,然后提供了处理触摸的具体功能,以及CoreAnimation
高级接口。但是为什么iOS要基于UIView和CALayer提供两个平行的层级关系呢?为什么不⽤一个简单的层级来处理所有事情呢?原因在于要做职责分离,这样也能避免很多重复代码。在iOS和Mac OS两个平台上,事件和⽤户交互有很多地方的不同,基于多点触控的用户界⾯和基于⿏标键盘有着本质的区别,这就是为什么iOS有UIKit和UIView,但是Mac OS有AppKit和NSView的原因。他们功能上很相似,但是在实现上有着显著的区别。
实际上,这里并不是两个层级关系,而是四个,每一个都扮演不同的角色,除了视图层级和图层树之外,还存在呈现树和渲染树.
-
呈现树:呈现树包含了当前动画发生时候将要显示的值,例如你要给图层背景颜色设置新的值的时候,它会立即修改图层树里面相应的值。但是在呈现树里面背景颜色值在将要显示给用户的时候才被更新为新值。
-
渲染树:渲染树是私有的,你无法访问到,渲染树在渲染图层的时候使用呈现树的值,为了不阻塞主线程,渲染的过程是在单独的进程或线程中进行的,所以你会发现Animation的动画并不会阻塞主线程。
前⾯面已经讲过,UIView是对CALayer的封装,那为什么我们还要学习CALayer呢?因为UIView封装的 API在有些情况下并不能满足我们的需求,比如:
1.阴影,圆⾓角,边框
2.3D变换
3.⾮矩形范围
4.遮罩
5.⾮线性动画
##3、CALayer常用属性 我们可以进入CALayer.h中查看CALayer支持的属性,其中注解中标注Animation的属性表示支持隐式动画,当这些属性的值改变时系统自带了平滑过渡的动画效果(非根Layer支持)
下表列出了CALayer常用的属性:
属性 | 说明 | 是否支持隐式动画 |
---|---|---|
anchorPoint | 和中心点position重合的一个点,称为“锚点”,锚点的描述是相对于x、y位置比例而言的默认在图像中心点(0.5,0.5)的位置 | 是 |
backgroundColor | 图层背景颜色 | 是 |
borderColor | 边框颜色 | 是 |
borderWidth | 边框宽度 | 是 |
bounds | 图层大小 | 是 |
contents | 图层显示内容,例如可以将图片作为图层内容显示 | 是 |
contentsRect | 图层显示内容,例如可以将图片作为图层内容显示 | 是 |
contentsRect | 图层显示内容的大小和位置 | 是 |
cornerRadius | 圆角半径 | 是 |
doubleSided | 图层背面是否显示,默认为YES | 否 |
frame | 图层大小和位置,不支持隐式动画,所以CALayer中很少使用frame,通常使用bounds和position代替 | 否 |
zPosition | 图层的重叠顺序 | 是 |
hidden | 是否隐藏 | 是 |
mask | 图层蒙版 | 是 |
maskToBounds | 是否剪切超出父图层边界的部分,默认为NO | 是 |
opacity | 透明度 ,类似于UIView的alpha | 是 |
position | 图层中心点位置,类似于UIView的center | 是 |
shadowColor | 阴影颜色 | 是 |
shadowOffset | 阴影偏移量 | 是 |
shadowOpacity | 阴影透明度,注意默认为0,如果设置阴影必须设置此属性 | 是 |
shadowPath | 阴影的形状 | 是 |
shadowRadius | 阴影模糊半径 | 是 |
sublayers | 设置多个子图层 | 是 |
sublayerTransform | 子图层形变 | 是 |
transform | 图层形变 | 是 |
- 隐式属性动画的本质是这些属性的变动默认隐含了CABasicAnimation动画实现,详情大家可以参照Xcode帮助文档中“Animatable Properties”一节。
- 在CALayer中很少使用frame属性,因为frame本身不支持动画效果,通常使用bounds和position代替。
- CALayer中透明度使用opacity表示而不是alpha;中心点使用position表示而不是center。
- anchorPoint属性是图层的锚点,范围在(0~1,0~1)表示在x、y轴的比例,这个点永远可以同position(中心点)重合,当图层中心点固定后,调整anchorPoint即可达到调整图层显示位置的作用(因为它永远和position重合)
创建layer并添加到根layer上:
CALayer *layer = [[CALayer alloc] init];
layer.frame = CGRectMake(10, 320, 330, 200);
layer.backgroundColor = [UIColor blueColor].CGColor;
// 将图层添加到父图层
[self.view.layer addSublayer:layer];
填充图片内容,需要将 UIImage 桥接(__bridge)到CGImage:
layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"image4.jpg"].CGImage);
使用layer CATextLayer 子类填充文字:
CATextLayer *textLayer = [[CATextLayer alloc] init];
textLayer.frame = CGRectMake(10, 550, 300, 30);
textLayer.string = @"这是layer填充的文字内容";
//字体颜色
textLayer.foregroundColor = [UIColor blackColor].CGColor;
textLayer.backgroundColor = [UIColor redColor].CGColor;
textLayer.font = (__bridge CFTypeRef _Nullable)([UIFont systemFontOfSize:20 weight:500]);
textLayer.fontSize = 20;
textLayer.alignmentMode = @"center";
// textLayer.truncationMode = @"middle";
[self.view.layer addSublayer:textLayer];
##4、CALayer图层绘制
- 通过直接设置CALayer的contents属性进行图层绘制
CALayer *layer = [CALayer layer];
// layer.frame = CGRectMake(0, 100, 350, 200);
layer.backgroundColor = [UIColor orangeColor].CGColor;
// 1、bounds: 尺寸
layer.bounds = CGRectMake(0, 0, 220, 220);
// 2、position: 定位点
layer.position = self.view.center;
// 3、锚点、支点:决定layer上的哪个点在 position 点上,默认(0.5, 0.5),范围:(0,0) ~ (1,1)
layer.anchorPoint = CGPointMake(0.5, 0.5);
// 4、z方向的层级
layer.zPosition = 2;
// 5、设置圆角:为直径的一半时会成圆形
layer.cornerRadius = 110;
// 6、填充内容
layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"image1.jpg"].CGImage);
//设置背景颜色
layer.backgroundColor = [UIColor orangeColor].CGColor;
// 7、是否可以裁剪多余的图层
layer.masksToBounds = YES;
// 8、设置边框宽度和颜色
//layer.borderWidth = 5;
//layer.borderColor = [UIColor lightGrayColor].CGColor;
[self.view.layer addSublayer:layer];
}
实现效果:
需要注意的是上面代码中绘制图片圆形裁切效果时如果不设置masksToBounds是无法显示圆形,但是对于其他图形却没有这个限制。原因就是当绘制一张图片到图层上的时候会重新创建一个图层添加到当前图层,这样一来如果设置了圆角之后虽然底图层有圆角效果,但是子图层还是矩形,只有设置了masksToBounds为YES让子图层按底图层剪切才能显示圆角效果。
- 扩展--带阴影效果的圆形图片裁切
如果设置了masksToBounds=YES
之后确实可以显示图片圆角效果,但遗憾的是设置了这个属性之后就无法设置阴影效果。因为masksToBounds=YES
就意味着外边框不能显示,而阴影恰恰作为外边框绘制的,这样两个设置就产生了矛盾。要解决这个问题不妨换个思路:使用两个大小一样的图层,下面的图层负责绘制阴影,上面的图层用来显示图片。
//阴影图层
CALayer *layerShadow = [[CALayer alloc]init];
layerShadow.bounds = CGRectMake(0, 0, 220, 220);
layerShadow.position= self.view.center;
layerShadow.cornerRadius = 110;
//阴影颜色
layerShadow.shadowColor = [UIColor grayColor].CGColor;
//阴影偏移量
layerShadow.shadowOffset = CGSizeMake(5, 1);
//阴影透明度
layerShadow.shadowOpacity = 1;
layerShadow.borderColor = [UIColor whiteColor].CGColor;
layerShadow.borderWidth = 5;
[self.view.layer addSublayer:layerShadow];
}
实现效果:
##5、CALayer3D变换
- 隐式动画: 当layer的属性发生变换时会默认产生动画效果,动画时间0.25s
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch=[touches anyObject];
CGFloat width=layer.bounds.size.width;
if (width==Width) {
width=Width*2;
// 组合 CATransform3D
CATransform3D transform_01 = CATransform3DScale(layer.transform, 1, 1, 1);
CATransform3D transform_02 = CATransform3DRotate(layer.transform, M_PI/6, 0, 0, 1);
layer.transform = CATransform3DConcat(transform_01, transform_02);
layer.cornerRadius = width/2;
layer.backgroundColor = [UIColor blueColor].CGColor;
}else{
width=Width;
layer.cornerRadius = 0;
layer.backgroundColor = [UIColor orangeColor].CGColor;
}
layer.bounds=CGRectMake(0, 0, width, width);
layer.position=[touch locationInView:self.view];
}
@end
实现效果:
- CATransform3D数据结构定义了一个三维变换(4x4 CGFloat值的矩阵),用于图层的旋转,缩放,偏移,歪斜和应用的透视,常用函数有下面几种:
1:CATransform3DMakeRotation(CGFloat angle, <#CGFloat x#>, <#CGFloat y#>, <#CGFloat z#>)
如果x=1,y=0,z=0则绕x轴旋转angle角度
如果x=0,y=1,z=0则绕y轴旋转angle角度
如果x=0,y=0,z=1则绕z轴旋转angle角度
如果x=1,y=1,z=0则绕x轴和y轴夹角旋转angle角度
如果x=1,y=1,z=1则绕3轴夹角旋转angle角度
上诉的旋转中心都是layer的锚点(anchorPoint)
2:CATransform3DRotate(<#CATransform3D t#>, <#CGFloat angle#>, <#CGFloat x#>, <#CGFloat y#>, <#CGFloat z#>)
功能与上一函数类似,但可以叠加一个CATransform3D效果
3:CATransform3DMakeScale(<#CGFloat sx#>, <#CGFloat sy#>, <#CGFloat sz#>)
用于缩放,三个参数是x轴,Y轴,z轴上的缩放程度,缩放中心是layer的锚点
4:CATransform3DScale(<#CATransform3D t#>, <#CGFloat sx#>, <#CGFloat sy#>, <#CGFloat sz#>)
功能与上一函数类似,但可以叠加一个CATransform3D效果
5:CATransform3DMakeTranslation(CGFloat tx, <#CGFloat ty#>, <#CGFloat tz#>)
用于平移
6:CATransform3DTranslate(<#CATransform3D t#>, <#CGFloat tx#>, <#CGFloat ty#>, <#CGFloat tz#>)
用于移动
7:CATransform3DConcat(CATransform3D a, <#CATransform3D b#>)
将两个CATransform3D效果叠加起来
我们也可以通过KVC更方便的设置transform的属性
[layer setValue:@M_PI forKeyPath:@"transform.rotation.x"];
可以通过KVC设置以下与transform相关的属性
transform.rotation.x
``transform.rotation.y`
transform.rotation.z
transform.scale.x
transform.scale.y
transform.scale.z
transform.translation.x
transform.translation.y
transform.translation.z
##6、CALayer事务 CALayer中"Animatable"属性变化都在CATrasaction的管理内,之前提到的属性支持隐式动画是指在某次Runroop中修改"Animatable"时,如果没有设置事务,则会自动创建一个CATransaction,并在当前线程的下一个RunLoop中commit这个CATransaction。
事务可以被嵌套,允许你禁用部分动画的行为或者在属性被修改的时候产生 的动画使用不同的时间。仅当最外层的事务被提交的时候,动画才会发生。
事务开启:[CATransaction begin]
禁止动画效果:[CATransaction setDisableActions:YES];
事务提交:[CATransaction commit]
我们可以通过事务控制动画的时长,甚至禁止动画效果,还可以设置completionBlock,当当前CATransaction的所有动画执行结束后,,completionBlock会被调用
##Demo下载地址: https://github.com/fuxinto/HfxDemo