在iOS中,按钮、文本框、图片等所有能看的见的和处理事件响应的基本都是UIView
。但是UIView
之所以能够显示到屏幕上,完全是因为它内部的图层(Layer)。
一、CALayer介绍
在创建UIView
对象时,UIView
内部会自动创建一个图层,这个图层就是CALayer
对象,通过UIView
的layer
属性可以访问到这个属性。layer
属性从来不会是空的,而且官方把UIView
描述为是图层的一个代理。
1 | @property(nonatomic,readonly,strong) CALayer *layer; |
当UIView
需要显示到屏幕上时,会调用drawRect:
方法进行绘图,并且会把所有内容都绘制到自己的图层上,绘图结束后,系统会把图层拷贝到屏幕上,所以我们就看到了UIView
的内容。所以UIView
本身不具备显示功能,是它内部的layer
才有显示功能。
1.1. 基本使用
通过操作CALayer
对象,可以很方便地调整UIView
的一些外观属性。例如,阴影,圆角、边框、内容等。也可以给图层添加动画,很多动画都是基于图层实现的。
1 | // 1.设置边框 |
给UIImageView
添加同样的属性,看下效果:
为什么没有裁剪成圆呢?这是因为UIImageView
的图片是在layer
里面的contents
上面的。而cornerRadius
是操纵layer
的,无法直接操纵contents
上面的内容,所以看到的效果就是没有裁剪。
如果要对图片进行裁剪,需要手动的调用一个方法,把超过视图的所有内容都裁剪掉,但是这样也会把阴影也裁掉,因为阴影本身也是在视图外的。
1 | view.clipsToBounds = YES; |
还有一个针对layer
的裁剪方法,其实clipsToBounds
本质也操作的layer
:
1 | view.layer.masksToBounds = YES; |
1.2. 离屏渲染
用上面的两种方法会造成离屏渲染,裁剪图像建议使用Quartz2D。
什么是离屏渲染?
处理图像分为CPU和GPU两部分,GPU是专门处理图像的。但是本应该交给CPU处理的事情交给了GPU处理,GPU又不擅长处理CPU的事情,所以GPU又开辟了一块新的内存专门处理CPU交代的事情。等处理完成后,GPU和CPU需要把结果合并到一起,在合并过程中比较消耗性能的,这就造成了离屏渲染。
真正造成离屏渲染的是masksToBounds
。一两个视图的圆角处理可以忽略,但是如果一个tableView
上大量使用masksToBounds
就会造成卡顿,这时候使用Quartz2D就不会造成离屏渲染了,这也是tableView
优化的一部分。
UI控件的位置和尺寸如果出现小数点可能会出现锯齿现象,这时候也会造成离屏渲染。所以要尽量避免出现小数点。
YY大神写的一篇文章非常深入透彻 -> https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/
二、CATransform3D
UIView
形变属性transform
是CGAffineTransform
类型。CALayer
形变属性transform
是CATransform3D
类型,同样也是一个结构体(CATransform3D
是四维方形矩阵)。
CA代表CoreAnimation
框架,CG代表CoreGraphics
框架。
1 | @property CATransform3D transform; |
CALayer
的transform
和UIView
的用法很相似,只是参数不一样。
3D即代表是个三维空间,坐标系有x、y、z轴。
2.1. 旋转、平移、缩放
2.1.1. 旋转
angle
是旋转角度,x、y、z分别代表围绕哪个坐标轴旋转。
绕哪个轴旋转就把对应坐标轴设为1,其他为0。
- 绕x轴旋转:
(angle, 1, 0, 0)
- 绕y轴旋转:
(angle, 0, 1, 0)
- 绕z轴旋转:
(angle, 0, 0, 1)
UIView
的transform
旋转就是对根Layer进行z轴旋转。
注意:旋转操作会自动根据最短路径旋转(例如,围绕z轴旋转270°,会逆时针旋转90°;旋转180°时也是逆时针旋转的;旋转45°是顺时针旋转)
1 | CATransform3D CATransform3DMakeRotation (CGFloat angle, CGFloat x, CGFloat y, CGFloat z); |
2.1.2. 平移
x、y分表代表二维偏移量,z代表层级,层级高会遮盖层级低的视图。
1 | CATransform3D CATransform3DMakeTranslation (CGFloat tx, CGFloat ty, CGFloat tz); |
2.1.3. 缩放
缩放操作的是x、y,z轴一般默认1。
1 | CATransform3D CATransform3DMakeScale (CGFloat sx, CGFloat sy, CGFloat sz); |
2.1.4. KVC
以上操作还可以通过KVC
进行设置:
1 | NSValue *value = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI_4, 0, 0, 1)]; |
使用KVC
场景:主要用来做快速形变操作,即只有一个值的操作。
例:
只做x轴的平移操作:
1 | [self.testImgView.layer setValue:@(100) forKeyPath:@"transform.translation.x"]; |
只做缩放操作:
1 | [self.testImgView.layer setValue:@(1.2) forKeyPath:@"transform.scale"]; |
只做绕z轴旋转操作:
1 | [self.testImgView.layer setValue:@(M_PI) forKeyPath:@"transform.rotation.z"]; |
二、自定义CALayer
CALayer
的创建形式和UIView
类似。
2.1. 创建CALayer
1 | CALayer *layer = [CALayer layer]; |
2.2. CALayer的内容
CALayer
的内容填充最具代表性的是图片,CALayer
的contents
属性就是对内容进行填充,默认为空。
图片会自动填充layer,不需要设置大小。但是需要CGImageRef
类型,因此需要对UIImage
进行转换。
1 | CALayer *layer = [CALayer layer]; |
2.3. 扩展
为什么CALayer
的背景色和内容需要转换为CoreGraphics
支持的类型。
CALayer
是定义在QuartzCore
框架中的;CGImageRef
、CGColorRef
是定义在CoreGraphics
框架中的;UIColor
、UIImage
定义在UIKit
框架中。
QuartzCore
和CoreGraphics
两个框架是跨平台的(MaxOS和iOS),但是UIKit
只能在iOS中使用。为了保证可移植性,QuartzCore
只能使用CGImageRef
、CGColorRef
。
三、CALayer和UIView的区别
通过CALayer
能够做出和UIImageView
一样的界面效果。既然CALayer
和UIView
都能实现实现相同的效果,那开发中选择谁更好呢?
CALayer
继承自NSObject
,而UIView
继承自UIResponder
。所以UIView
比CALayer
多了一个事件处理的能力,也就是说CALayer
不能处理用户的触摸事件。
如果显示的内容需要和用户进行交互,使用UIView
;如果不需要交互,两者都可以,但建议选择CALayer
,因为 CALayer
的性能更好,更加轻量级。
反转,反转,实际开发中即使不需要事件处理,也建议使用UIView
,不是自相矛盾么?因为 UIView
可扩展性更好,开发效率会更高。
三、position和anchorPoint的作用
position
和anchorPoint
是CALayer
的两个非常重要的属性。
position
用来设置CALayer
在父层中的位置。以父层左上角为原点坐标(0,0)
。
1 | @property CGPoint position; |
anchorPoint
是锚点(定位点),决定着CALayer
的哪个点会在position
属性所指的位置。锚点的坐标系是自己本身,以自己左上角为原点坐标(0,0)
,取值范围是[0, 1]
,默认值是(0.5, 0.5)
。
1 | @property CGPoint anchorPoint; |
position
和anchorPoint
是始终重合的。
示例
场景:绘制一个红色layer,查看position
和anchorPoint
坐标点。
步骤一:初始化位置
1 | CALayer *layer = [CALayer layer]; |
步骤二:修改position
1 | - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { |
步骤三:修改anchorPoint
1 | - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { |
通过输出发现:position
和anchorPoint
始终在一个点上,无论修改哪个属性值都会让layer的位置发生改变。默认情况下,layer相对父layer的左上角坐标点是(layer.position.x-layer.width/2, layer.position.y-layer.height/2)
,UIView
的center
就是内部layer的position
。anchorPoint
的可移动范围就是layer的自身大小,
四、隐式动画
每一个UIView
内部都默认关联着一个CALayer
,我们称这个layer为RootLayer
(根层)。所有非RootLayer
(手动创建的CALayer
对象)都存在隐式动画。
通过上面的案例发现,修改自定义layer的部分属性时会有一个动画效果,这个效果是系统自己添加的,这个动画就是隐式动画。属性是否携带动画,苹果在文档注释中都有描述,携带Animatable
的就是会产生隐式动画。常见的会产生隐式动画的属性有bounds
、backgroundColor
、position
。
取消隐式动画
隐式动画的本质是封装的一个事务,如果要取消隐式动画只需要把事务取消就可以了。
1 | [CATransaction setDisableActions:NO]; |
把需要取消隐式动画的属性操作放在上面代码的后面就可以了。
包装隐式动画
如果需要部分属性执行隐式动画,只需要使用事务把这部分属性上下包住就行了。
1 | // 1.开启事务 |
例:部分属性添加隐式动画
1 | // 隐式动画时长 |