1.CALayer 与 UIView 的区别:
它们有一些方法和属性用来做动画和变换。和UIView最大的不同是CALayer不处理用户的交互。
CALayer并不清楚具体的响应链(iOS通过视图层级关系用来传送触摸事件的机制),于是它并不能够响应事件,即使它提供了一些方法来判断是否一个触点在图层的范围之内
除了视图层级和图层树之外,还存在呈现树和渲染树
我们已经证实了图层不能像视图那样处理触摸事件,那么他能做哪些视图不能做的呢?这里有一些UIView没有暴露出来的CALayer的功能:
阴影,圆角,带颜色的边框
3D变换
非矩形范围
透明遮罩
多级非线性动画
一个视图(UIView)只有一个相关联的图层(layer)(自动创建),同时它也可以支持添加无数多个子图层
2.CALayer 的 寄宿图
CALayer类能够包含一张你喜欢的图片,这一章节我们将来探索CALayer的寄宿图(即图层中包含的图)
contents属性:
在实践中,如果你给contents赋的不是CGImage,那么你得到的图层将是空白的.
事实上,你真正要赋值的类型应该是CGImageRef,它是一个指向CGImage结构的指针。UIImage有一个CGImage属性,它返回一个"CGImageRef",如果你想把这个值直接赋值给CALayer的contents,那你将会得到一个编译错误。因为CGImageRef并不是一个真正的Cocoa对象,而是一个Core Foundation类型。
尽管Core Foundation类型跟Cocoa对象在运行时貌似很像(被称作toll-free bridging),他们并不是类型兼容的,不过你可以通过bridged关键字转换。如果要给图层的寄宿图赋值,你可以按照以下这个方法:
layer.contents = (__bridge id)image.CGImage;
.
注意:Core Foundation类型与Cocoa对象运行时很像,但是不一样,可以通过(__bridge id)方法来转换
contentsRect在app中最有趣的地方在于一个叫做image sprites(图片拼合)的用法。
利用Interface Builder探测窗口 的 Stretching控制contentsCenter属性
虽然-drawRect:方法是一个UIView方法,事实上都是底层的CALayer安排了重绘工作和保存了因此产生的图片。
3.图层几何学
UIView有三个比较重要的布局属性:frame,bounds和center,CALayer对应地叫做frame,bounds和position。为了能清楚区分,图层用了“position”,视图用了“center”,但是他们都代表同样的值。
对于视图或者图层来说,frame并不是一个非常清晰的属性,它其实是一个虚拟属性,是根据bounds,position和transform计算而来,所以当其中任何一个值发生改变,frame都会变化。相反,改变frame的值同样会影响到他们当中的值
hitTest:方法同样接受一个CGPoint类型参数,而不是BOOL类型,它返回图层本身,或者包含这个坐标点的叶子节点图层。
4.coreAnimation并不是一个绘图系统。它只是一个负责在硬件上合成和操纵应用内容的基础构件。
CoreAnimation核心是图层对象,图层对象用于管理和操控你的应用内容。
图层将捕获的内容放到一副位图中,图形硬件能够非常容易的操控你的位图。
图层被作为一种管理视图内容的方式,但是你也可以创建标准的图层,这取决于你自身的需要。
动画分为:隐式动画和显示动画
隐式动画是一种从旧属性值动画到新属性值的动画形式。
******基于图层的绘图模型******
对基于视图的绘图,对视图的改变经常会触发调用视图的drawRect:方法以重绘视图内容。但是此种方式的代价相对较高,因为它是CPU在主线程上的操作。Core Animation通过尽可能的使用图形硬件操纵缓存后的位图来避免了这种开销,从而完成相同或相似的效果。
****基于图层的动画****
可在图层上执行的动画类型:移动,缩放,旋转,透明度,圆角半径,背景颜色。
图层对象定义了自己的几何结构
****图层使用两种类型的坐标系统****
图层利用基于点的坐标系统和单位坐标系统指定内容的布局,当指定的值是直接映射到屏幕或相对于其他图层的坐标,图层的position属性,则使用基于点的坐标系统,与屏幕坐标不相关联,则使用单位坐标。比如图层的anchorPoint属性。
锚点是使用单位坐标系统的属性之一。
锚点影响几何结构的操作
position属性是相对于图层的锚点被指定。并且任何你对图层阴影的变换操作也是相对于锚点。
AnchorPoint相对于自身wh的百分比,0.0~1.0,position是相对于父视图的坐标点。
5.CoreAnimation
作用不仅仅是做动画。
在iOS端,我们看到的一切都是由Core Animation来完成的。
但Core Animation并不做实际的绘制渲染的工作,实际的绘制渲染工作由绘制渲染系统(GPU)来完成,Core Animation是一个中间层框架。
Core Animation为其它框架提供数据和传递数据,当然Core Animation也会对数据进行处理,像坐标,矩形计算,创建OpenGL纹理等。
iOS的整个显示流程没UIKit什么事,这跟Mac OS的处理是有所不同的,iOS的显示是默认建立在Core Animation之上,显示层的处理是通过Core Animation来完成,而Mac OS并不依赖于Core Animation。由于Core Animation在iOS端的位置,所以你如何使用Core Animation这将会对性能产生极大的影响。
Core Animation动画有两种形式,分别是显式动画和隐式动画。
但不管以哪种形式实现动画,都是基于CAAnimation来实现的,所以这两种动画形式的主要差异在于,是否显式创建CAAnimation对象来实现动画。当然它们还存在其它的差异,例如,隐式动画是基于CABasicAnimation对来实现的,而显式动画可以有更多的选择,可以是CABasicAnimation,CAKeyframeAnimation,CATransition等。
// 隐式动画
var layer = CALayer.init()
layer.backgroundColor = UIColor.black.cgColor
// 显示动画
var animation = CABasicAnimation.init(keyPath: "backgroundColor") // 显示创建CAAnimation对象
animation.fromValue = layer.backgroundColor
animation.toValue = UIColor.black.cgColor
layer.backgroundColor = UIColor.black.cgColor
layer.add(animation, forKey: nil)
对于CALayer
而言,隐式动画是默认开启的,只需要更改可动画属性就会触发隐式动画。但对于UIView
的Backing Layer
而言,隐式动画是默认关闭的。当更改独立CALayer
的可动画属性时,Core Animation
会为我们创建默认的动画对象来实现相应的动画,但Core Animation
又以什么作为标准来实现这些动画呢?隐式动画的大部份参数由事务(CATransaction
)来决定,其中包括,动画时间,动画缓冲等。
虽然隐式动画的大部份参数由CATransaction
来决定,但我们奇怪地发现,在代码中并没有出现CATransaction
,WHY?这是由于CATransaction
和NSAutoreleasePool
一样,也分为隐式CATransaction
和显式CATransaction
,而CATransaction
对隐式动画的管理方式与NSAutoreleasePool对内存的管理方式也十分相似,[CATransaction begin]
方法是CATransaction
的开始,而[CATransaction commit]
则是CATransaction
的结束,中间便是CATransaction
的作用域,需要把更改可动画属性的操作放在CATransaction
的作用域内,Core Animation
才会创建相应的隐式动画。
[CATransaction begin];
// do something
[CATransation commit];
这个上面为CATransation的显示创建。显式创建CATransaction常常被用于关闭隐式动画(独立的CALayer对象默认开启隐式动画,需要手动关闭)和调整动画的时间。
隐式CATransaction是在RunLoop的一次Loop开始时begin,Loop结束时commit,正因为有隐式CATransaction的存在,所以当你更改独立CALayer的可动画属性时,Core Animation会为此创建隐式动画。CATransaction和NSAutoreleasePool一样,可以嵌套处理。
不管是显式动画还是隐式动画,动画属性的初始值要不同于目标值才会产生动画。Core Animation动画过程中,显示在屏幕上的是呈现树
的对象,而不是图层树
的对象。
6.UIKit动画
UIKit动画和Core Animation动画是相关连的,它们的关系就好像NSOperation和GCD一样,UIKit动画是建立在Core Animation动画之上的。
UIView对象的Backing Layer的隐式动画是默认关闭的,如果想开启需要使用UIKit的事务。
// 开启UIKit隐身动画
// 方式1
UIView.beginAnimations(nil, context: nil)
// do something
UIView.commitAnimations()
// 方式2
UIView.animate(withDuration: 0.25) {
// do something
}
UIKit事务继承了Core Animation事务(CATransaction)的所有功能,并且在此基础上做了许多扩展,像触摸响应,更多的动画对象等。
作为Core Animation动画的一个抽象物,UIKit虽然提供了非常简便的API来实现动画,但UIKit动画毕竟是Core Animation动画的一个抽象物,能提供的效果还是很有限的。
7.Core Animation动画与触摸响应
当我们触摸屏幕时,系统就会把这个触摸事件传递给相应Application,Application接收到触摸事件后,执行_UIApplicationHandleEventQueue()函数(这是一个Source0的回调),把触摸事件转换成UIEvent并开始传递和处理事件。
Application处理触摸事件主要分为以下两步:
1).获取Hit-Test View。
2).寻找响应者处理事件。
响应链是由一组连接在一起的响应者组成,UIResponder是所有响应者的基类,响应者的类型不是唯一的,不同的系统事件对应着不同类型的响应者,而触摸事件的响应者主要是UIApplication对象,UIViewController对象和UIView对象,UIApplication对象是响应链的第一个节点,也是最后一个响应事件的对象。
以上两步操作由两个不同的函数完成,_UIApplicationHandleDigitizerEvent()函数用于获取Hit-Test View,[UIApplication sendEvent:]函数用于查找可以处理事件的响应者,当然这两个函数是在_UIApplicationHandleEventQueue()中被调用的,调用关系如下图。
_UIApplicationHandleDigitizerEvent()函数通过调用UIView对象的hitTest: withEvent:函数来获取Hit-Test View。
一个UIWindow的hitTest: withEvent:函数返回值是当前Window的Hit-Test View,Hit-Test View是响应链中第一个被检测是否能处理触摸事件的对象。获取Hit-Test View后,调用[UIApplication sendEvent:]函数从响应链中寻找响应者处理事件,当找到响应者处理触摸事件后,本次触摸事件处理结束(当然,你也可以通过重写一些函数使事件继续传递)。
即使不发生触摸事件,我们也可以通过nextResponder来获取上一级响应者。
UIKit在UIResponder对象关系确立的同时生成响应链,响应链与视图的层级结构也是一一对应的,一般情况下,父视图是子视图的上一级响应者,当然,也有一些特殊的情况,像UIViewController根视图的上一级响应者是UIViewController而不是父视图。
正常情况下,判断触摸点是否在当前图层树对象的坐标系中用的是pointInside: withEvent:函数。也有一些特殊的情况,如果一个视图正在实现Core Animation动画且动画过程中不允许交互(默认不交互),那么该视图不再调用pointInside: withEvent:函数来判断触摸点,而是获取动画视图的呈现树对象,判断触摸点是否在该呈现树对象的坐标系中。若Hit-Test View正在实现Core Animation动画且动画过程中不允许交互,那么本次触摸事件将不处理且不再寻找响应者处理事件。
若视图正在实现Core Animation动画且动画过程中允许交互(动画过程中允许交互需要添加UIViewAnimationOptionAllowUserInteraction),那么不能调用pointInside: withEvent:函数来判断触摸点是否在当前图层树对象的坐标系中。
pointInside: withEvent:函数用于判断的对象是图层树中的对象,而Core Animation动画过程中,显示在屏幕上的是呈现树的对象,而不是图层树的对象,但由于图层树对象存储的是目标值,而呈现树对象存储的是瞬时值。
呈现树:Presentation Layer
图层树:Model Layer
8.定时动画
除了Core Animation动画,iOS动画还有其它实现的方式,像定时动画,手势动画等。
定时动画与Core Animation动画不同,定时动画并不是基于CAAnimation来实现的,而是通过定时器的触发不断地改变图层树对象的UI属性值来实现的,每次定时触发所改变的UI属性都会被Application发送到渲染服务进程,渲染服务进程就会把最新的图层树渲染到屏幕上。通过定时器的触发不断地重复这些步骤,最终在屏幕上形成了动画。
就是通过timer不断刷新view的frame
如果你想控制动画的每一帧,完全自定义动画的缓冲,那么定时动画是一个非常不错的选择,就像ScrollView一样,ScrollView的滚动就是一个定时动画,正因为如此,所以TableView才可以实时更新Cell的内容并显示出来。这种实时同步图层树的方式,既是定时动画的优点,也是定时动画的缺点,缺点在于一旦实时同步的操作时间过长就会出现屏幕掉帧的情况,最常见的例子就是TableView在滚动时出现卡屏掉帧。(原理:定时动画)
Core Animation动画VS定时动画
不管以那种形式实现动画,动画都是一帧帧实现的,Core Animation动画与定时动画主要的不同点在于,Core Animation动画是以CAAnimation对象为基础的动画,Application通过发送图层树和CAAnimation对象到渲染服务进程,由渲染服务进程完成动画所需要的每一帧;
而定时动画则是通过定时器的解发,不断地改变图层树,Application把每次改变后的图层树发送到渲染服务进程进行屏幕的重新渲染。
一般情况下,Core Animation动画会是更好的选择,不仅是因为实现Core Animation动画相对简单,而且动画的每一帧由渲染服务进程完成,Application有更多的空间来完成其它任务,提高CPU的效率;
当然,你也可以使用一些物理框架来实现定时动画,自定义动画的缓冲,实现更真实的效果。更多的时候是需要根据界面的情况来选择实现动画的形式,并没有一种形式适用于任何情况,关键在于如何减少掉帧,保持界面流畅度。
一般情况下,Core Animation动画会是更好的选择,不仅是因为实现Core Animation动画相对简单,而且动画的每一帧由渲染服务进程完成,Application有更多的空间来完成其它任务,提高CPU的效率;当然,你也可以使用一些物理框架来实现定时动画,自定义动画的缓冲,实现更真实的效果。更多的时候是需要根据界面的情况来选择实现动画的形式,并没有一种形式适用于任何情况,关键在于如何减少掉帧,保持界面流畅度。
终结:
在iOS端,你在屏幕上看到的一切都是由Core Animation来完成的,当你打算往显示方面进阶时,你需要深入研究的是Core Animation而不是UIKit,就像Instruments里没有UIKit测试工具一样,虽然UIKit抽象了很多Core Animation的功能,但同时也带来了很多限制,有时候你更需要的是一些自定义效果。