CALayer概念
CALayer 继承自 NSObject,CALayer是在3D空间中的2D曲面,是 Core Animation 的核心。它提供了 绘图 和 动画 的基础。
与 UIView 一样,CALayer 管理几何图形(如:位置,大小和变换)、内容和表面的视觉属性(如:背景颜色,边框和阴影);与 UIView 不同的是,CALayer 不定义自己的外观,仅管理位图周围的状态信息。位图可以是视图本身绘图的结果,也可以是您指定的固定图像的结果。
CALayer 的主要作用是管理基于图像的内容,并对该内容执行动画。
1 | open class CALayer : NSObject, NSSecureCoding, CAMediaTiming |
CALayer 通过 CALayerDelegate 类型的 delegate 属性,提供图层的内容;处理子图层的布局;提供要执行的自定义动画动作。如果图层对象是由 UIView 创建的,必须将 delegate 属性设置为拥有该图层的视图。
CALayer 通过遵守 CAMediaTiming 协议,来封装图层及其动画的持续时间和节奏,该协议定义了图层的计时信息。
在 iOS 中, Core Animation总是处于启用状态,每个 UIView 都有一个 CALayer 支持。在 macOS 中,应用程序必须通过操作显式启用核心动画支持。
CALayer的绘图模型
大多数情况下 CALayer 不会进行任何实际绘制,而是捕获 UIView 的内容并将其缓存在位图中,位图有时也称为后台存储。
当随后更改 CALayer 的属性时,您所做的只是更改与 CALayer 对象关联的状态信息。当更改触发动画时,Core Animation 将 CALayer的位图和状态信息传递给图形硬件(graphics hardware),由图形硬件使用新信息渲染位图,其过程如下图所示:
在硬件中操作位图产生的动画比在软件中要快得多。
基于 CALayer 的绘制与更传统的基于 UIView 的绘制技术有很大的不同。Core Animation 是直接操作静态位图来绘制内容;而对 UIView 本身的更改,需要调用 UIView 的 drawRect: 方法来使用新的参数重新绘制内容,这一过程是使用主线程上的CPU来完成的,其绘制方式是昂贵的。
Core Animation 会尽可能通过在硬件中操作缓存的位图来达到相同或类似的效果,从而避免了这种开销。
CALayer动画
下图是在 CALayer 上执行的几种动画类型,可参考触发动画CALayer动画属性进行相关动画。
CALayer的两种类型坐标系
CALayer 同时使用基于 点的坐标系 和 单位坐标系 来指定内容的放置。
基于点的坐标系,最常见的用途是指定图层的大小和位置,您可以使用 CALayer 的 bounds 和 position 属性进行指定。CALayer 的 frame 属性实际上是从bounds和position属性中的值派生的,使用频率较低。
单位坐标系,可以将单位坐标视为指定值的百分比,每个坐标值的范围为0.0至1.0。使用 单位坐标系的属性有:锚点(anchorPoint)、Core Animation的相关属性
CALayer特有功能
CALayer未通过UIView暴露出来的功能:
- 阴影,圆角,带颜色的边框 3D变换
- 非矩形范围
- 透明遮罩
- 多级非线性动画
CALayer 的属性和方法
图层的几何坐标
CALayer的 bounds、position、frame 对应着 UIView 的 bounds、center、frame。当操作视图的frame时,实质上是操作下方 CALayer 的frame,不能独立于图层来操作视图的frame。
frame:表示图层父层坐标系中的位置和大小。它是一个计算属性,它是从Bounds、anchorPoint、position和transform属性中的值计算而来,其中任意一个属性改变都会影响frame。bounds:表示图层在自身坐标系中的位置和大小。position:用来设置CAlayer在父层中的位置,默认值为.zero,以父层的左上角为坐标原点。anchorPoint:称为定位点、锚点,对CALayer的几何操作都围绕该点进行。它采用单位坐标系,默认值为(0.5, 0.5),表示layer矩形的中心。anchorPoint决定CALayer身上的那个点会在position属性所指的位置。
坐标转换
1 | func convert(_ p: CGPoint, from l: CALayer?) -> CGPoint |
zPosition、anchorPointZ
CALayer存在一个三维空间当中,下面两个属性都是表示图层在 Z轴 的位置。
zPosition:主要用于改变图层的显示顺序,或者在三维空间移动和旋转图层。anchorPointZ:表示图层在Z轴上的定位点,默认值为0。
图层的内容
CALayer 是管理应用程序提供的内容的数据对象。CALayer的内容由一个位图组成,其中包含你想要显示的可视化数据。有如下三种方式提供 CALayer 内容:
- 直接给
CALayer的contents属性赋值 图像对象;(此技术最适用于从不更改或很少更改的层内容) - 给
CALayer分配一个delegate对象,让代理绘制层的内容。(此方式适合于可能周期性变化且可以由外部对象(如视图)提供的层内容。) - 定义一个
CALayer的子类并重写它的一个绘图方法来提供自己的层内容。(此方式适用于,当你必须创建一个自定义的图层子类,或者你想改变图层的基本绘制行为)
contents
iOS中必须给 contents 赋值CGImage类型,否则会显示空白。contents值的类型为Any?,是因为在 MacOS 中,这个属性对 CGImage 和 NSImage 类型的值都起作用.
除了通过 contents 给图层设置寄宿图,我们还可以通过 Core Graphics 进行自定义绘制。
1 | redLayer.contents = UIImage(named: "car_icon")?.cgImage |
contentsGravity
表示图层的内容如何在其边界内定位或缩放,取值分为两类:
- position-based gravity constants
允许您在不缩放图像的情况下将图像固定到层边界矩形的特定边缘或角落。
1 | .center / .top / .bottom / .left / .right |
- scaling-based gravity constants
允许你使用几个选项中的一个来拉伸图像,其中一些保留了高宽比,而另一些则没有。
1 | .resize // 默认值 |
1 | redLayer.contentsGravity = .center |
contentsScale
contentsScale定义CALayer的逻辑坐标空间(以点为单位)和物理坐标空间(以像素为单位)之间的映射。
该属性属于支持高分辨率屏幕机制的一部分,默认值为 1.0,表示以每个点1个像素绘制图片,一般设置其值为屏幕的scale。比如:当layer.size = (100,100),scale = 2.0,则该layer最多可以显示200 pixels的图片。
只有在直接为图层的contents属性指定位图时,才需要更改contentsScale 属性的值。UIKit和AppKit中的层支持视图根据屏幕分辨率和视图管理的内容自动将其层的比例因子设置为适当的值。
如果对 contentsGravity 进行了 resize 缩放设置,则该属性无效。
1 | redLayer.contentsScale = UIScreen.main.scale |
masksToBounds
该属性默认为false,为true表示裁剪掉超出边界的内容
1 | redLayer.masksToBounds = true |
contentsRect
表示单位坐标空间中的矩形,它使用单位坐标(0~1),定义使用图层内容的一部分。默认值是 (0.0, 0.0, 1.0, 1.0),表示图层的所有内容可见。
可以通过该属性,进行 图片拼合(image sprites),把几张图片载入到一张大图,然后赋值给几个独立的 layer.contents,进行调整layer.contentsRect 来分别显示不同的部分内容。这样做的好处:减少内存使用、载入时间、渲染性能等等。
1 | // 显示水平方向一般,垂直方向的全部内容 |
contentsCenter
contentsCenter表示,当 layer.contentsGravity = .resize时,即图层内容大小可以被调整,如何对图层的内容矩形进行缩放。此属性也是采用 单位坐标 表示其值。
1、该属性把图层内容分成 3x3 的网格。该属性的值指定了该网格中 中心矩形 的位置和大小,即定义了一个固定的边框和一个在图 层上可拉伸的区域。
2、layer.contentsGravity 设置为调整大小模式,则在调整大小时,会导致 3x3 网格中的矩形进行不同程度的缩放。
3、中心矩形在两个维度中都被拉伸,上中心和下中心矩形仅水平拉伸,左中心和右中心矩形仅垂直拉伸,四个角矩形根本不拉伸。
当 contentsCenter = (0,0,1.0,1.0)时,整个图片内容在两个维度上缩放。
该属性的效果和 UIImage 的 resizableImage(withCapInsets:, resizingMode:) 方法很相似
1 | redLayer.contents = UIImage(named: "chat_send_nor")?.cgImage |
自定义绘制
除了通过 layer.contents 设置图层的图片,还可以通过 Core Graphics 绘制 CALayer 的图像内容,有如下两种方式:
- 重写UIView的
draw(_ rect:)进行绘制。其底层还是CALayer完成,UIView只是进行一层封装 - 而对于在
CALayer中重绘制内容,则需要借助它的代理
1 | orangeLayer.delegate = self |
图层的外观设置
圆角边框
cornerRadius:设置图层的圆角半径,默认情况下它只作用于背景色和边框。若需要对图层的图像、子图层裁剪,需要借助
masksToBounds属性。borderColor:设置图层边框的颜色。
borderWidth:图层边框的宽度,默认0.0。当其值大于0.0时,会borderColor绘制边框。
阴影
如果开启了 masksToBounds = true ,阴影将被裁减。图层阴影是根据图层的内容、背景颜色、子图层计算出来的,而不是根据边框。
- shadowColor:阴影的颜色,默认是不透明的黑色。
- shadowOpacity:阴影的不透明度,0~1.0,默认0.0表示透明。
- shadowOffset:阴影的偏移量,默认为 (0.0, -3.0)
- hadowRadius :阴影层的模糊半径,默认为0.3
- shadowPath:指定阴影的形状,默认值为nil,即使用标准的阴影的形状。如果图层包含多个透明子图层,每个子图层还包含图片,这样计算阴影的时候是非常消耗资源的,可以通过该属性指定显式路径通常可以提高渲染性能
图层蒙版
- contentLayer.mask:该属性接收一个CALayer类型,定义了图层的可见区域。注意 mask 接收的 layer,它的frame是相对于其父图层的。
1 | let contentLayer = CALayer() |
isOpaque
isOpaque表示CALayer是否包含完全不透明的内容,默认值为false。
如果你的应用程序绘制了完全不透明的内容,填充了层的边界,将这个属性设置为true可以让系统优化层的渲染行为。
1 | var isOpaque: Bool { get set } |
Rasterization(栅格化、光栅化)
向量图形:是计算机图形学中用点、直线或者多边形等基于数学方程的几何图元表示图像。
位图:又称为栅格图、点阵图。是使用像素阵列(Pixel-array/Dot-matrix点阵)来表示的图像。
计算机显示器都要将矢量图形转换成栅格图像的格式,包含屏幕上每个像素数值的栅格图像保存在内存中。
- Rasterization(栅格化、光栅化):是PS中的一个专业术语,栅格即像素,用于任何将 向量图形 转换成 位图(栅格图像) 的过程。
栅格化在应用中通常表示在计算机上显示三维形状的流行渲染算法,是目前生成实时三维计算机图形最流行的算法。
最基础的栅格化算法将多边形表示的 三维场景 渲染到 二维表面。多边形由三角形的集合表示,三角形由三维空间中的三个顶点表示。在最简单的实现形式中,栅格化工具将顶点数据映射到观察者显示器上对应的二维坐标点,然后对变换出的二维三角形进行合适的填充。
shouldRasterize
shouldRasterize 表示 CALayer 在合成之前是否渲染为位图,即是否对 CALayer 进行栅格化。默认值为false。
如果值为true,则 CALayer 在其局部坐标系中渲染为位图,然后位图将合成到目标中。如果位图需要缩放,则应用 minificationFilter 和magnificationFilter 属性。
光栅化发生在 CALayer 的 filters(滤镜)和 shadow(阴影)被应用之后,但在 opacity(不透明度)调制之前。作为实现细节,渲染引擎(Rendering Engine)可能会尝试从一帧到下一帧缓存和重用位图,不管它是否会影响渲染输出。
如果值为false,CALayer将尽可能直接合成到目标中。但是,合成模型的某些特性可能会强制栅格化,例如添加过滤器(filters)。
1 | var shouldRasterize: Bool { get set } |
rasterizationScale
rasterizationScale 当 shouldRasterize 的值为 true 时,相对于CALayer的坐标空间栅格化内容的比例,即确定是否缩放光栅化的内容。默认值为1.0。
1 | var rasterizationScale: CGFloat { get set } |
拉伸过滤
minificationFilter 和 magnificationFilter 是指缩小 或者 增大图层图片大小时使用的 filters(过滤器)。它们使用的场景是:当一张图片需要显示不同大小(比如:缩略图和大图)
1 | .nearest |
专用图层
Core Animation提供了不同的图层类,每个图层类都提供了专门有用的功能。选择不同的图层类能够让你以简单的方式提高性能,或者支持特定类型的内容。CALayer类是所有层对象的根类。具体可以学习CALayer子类及其用途
CAShapeLayer
CAShapeLayer 用于在其坐标空间中绘制三次贝塞尔曲线样条的图层。CAShapeLayer 可以用来绘制所有能够通过 CGPath 来表示的形状。
也可以在
drawRect中用Core Graphics直接向原始的CALyer的内容中绘制一个路径,来达到此效果。但此方式占用CPU,消耗性能大;CAShapeLayer将对图形进行抗锯齿绘制,并在可能的情况下将其映射到屏幕空间,然后再进行栅格化以保持分辨率的独立性。CAShapeLayer通过在其合成时将提供的路径渲染为位图图像来创建其内容。这样做的好处是图层总是以尽可能最好的分辨率绘制路径,但这一好处是以额外的渲染时间为代价的。
在使用 CAShapeLayer 时,需要考虑如下问题:
如果您提供的路径很复杂,那么对该路径进行光栅化可能会花费太多。
如果层的大小频繁变化(因此必须频繁重绘),绘制所花费的时间就会增加,并成为性能瓶颈。
解决办法:可以将复杂的形状分解为简单的形状,来减少绘制的时间。
在合成器中,使用简单的路径,并将多个CAShapeLayer对象层叠在一起比绘制一个大的复杂路径要快得多。这是因为绘图操作时在CPU上进行的,而合成是在GPU上进行的。
1、CAShapeLayer根据路径来绘制形状
1 | let path = UIBezierPath(arcCenter: CGPoint(x: 25, y: 25), radius: 25, startAngle: 0, endAngle: -CGFloat.pi * 2, clockwise: false) |
2、CAShapeLayer绘制路径线段
1 | let linePath = UIBezierPath() |
3、CAShapeLayer填充路径时的规则
1 | let leftPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 80, height: 80)) |
如上介绍了 CAShapeLayer 的基本API,当然开发中我们使用 CAShapeLayer 远远不止这些,比如:给控件添加任意圆角;进度条 直线、圆环(纯色 or 渐变色);实现复杂的View的遮罩效果;实现一些动画等等。
CAGradientLayer
CAGradientLayer 是用于在背景上绘制渐变色的图层,用于填充图层的形状(包括圆角)。
.axial:轴向渐变,又称为线性渐变,垂直于坐标轴的直线上的所有点都有相同的颜色值。本例子效果如下:
1 | let gradientLayer = CAGradientLayer() |
.radial:径向渐变,渐变定义为椭圆,其中心位于 startPoint ,其宽度和高度分别由(endPoint.x - startPoint.x) * 2 和 (endPoint.y - startPoint.y) * 2 定义。本例子效果如下:
1
2
3gradientLayer.type = .radial
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 3.0/2.0, y: 1.0)
.conic:圆锥形渐变,渐变以 startPoint 为中心,然后以顺时针方向绕中心实现渐变效果。当 startPoint 和 endPoint 重叠时,结果是未定义的。本例子效果如下:
1 | // 3、圆锥形渐变 |
CATextLayer
CATextLayer 用于提供简单文本布局以及普通字符串或属性字符串的呈现的图层。
在渲染文本时,CATextLayer禁用亚像素反锯齿。当文本在栅格化的同时被合成到现有的不透明背景中时,只能使用亚像素反锯齿来绘制文本。在有了背景像素之前,没有办法自己用亚像素反锯齿来绘制文本,无论是图像还是层。将图层的不透明度属性设置为true不会改变渲染模式。
1、绘制普通文本
1 | let textLayer = CATextLayer() |
2、绘制富文本
1 | let textLayer = CATextLayer() |
使用CALayer的技巧提示
1、尽可能使用 Opaque Layers,将 layer.isOpaque = true 可以让 Core Animation 知道它不需要为图层维护一个alpha通道。
2、对CAShapeLayer对象使用更简单的路径
3、为相同的layer显式设置contents
4、始终将 CALayer 的 Size 设置为整数值
5、根据需要使用异步层渲染,layer.drawsAsynchronously = true
6、给 CALayer 添加阴影时指定阴影路径shadowPath
具体解释可以查看Improving Animation Performance