iOS 动画基础知识

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而言,隐式动画是默认开启的,只需要更改可动画属性就会触发隐式动画。但对于UIViewBacking Layer而言,隐式动画是默认关闭的。当更改独立CALayer的可动画属性时,Core Animation会为我们创建默认的动画对象来实现相应的动画,但Core Animation又以什么作为标准来实现这些动画呢?隐式动画的大部份参数由事务(CATransaction)来决定,其中包括,动画时间,动画缓冲等。

虽然隐式动画的大部份参数由CATransaction来决定,但我们奇怪地发现,在代码中并没有出现CATransaction,WHY?这是由于CATransactionNSAutoreleasePool一样,也分为隐式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的功能,但同时也带来了很多限制,有时候你更需要的是一些自定义效果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容