Core Animation库是iOS动画技术的基础,由一系列类与子类组成(他们基本都有个特点就是以各种CA开头,如CALayer,CAAnimation)。我们之前学习到的View动画,隐式Layer动画等,都是基于Core Animation
库的一个封装实现。Core Animation
又称显式动画,使用显式动画技术,我们可以更细致地定义我们要的动画的整个实现过程。
通过本章,我希望和大家一起不仅掌握iOS的动画编程,更掌握UI系统关于动画编程的核心技术与探索核心技术的方法。
学习显式动画,可以揭示iOS动画库实现的整个细节,他的设计思想与实现。
据我的总结,一般UI系统都支持如下几种动画:
第一种是 帧动画(Frame Animation)。这是最简单的动画,就是把动画拆成一帧帧图片连续播放,从而形成的动画。我们在Image Animation已经学习过了;
第二种是 属性动画(Property Animation)。这是最常见的动画方式,我们在Animation概述中介绍过,就是把动画的本质定义为视图某些属性随时间的变化。这个也是我们今天学习的主要动画方式。
第三种是 布局动画(Layout Animation),就是当布局时其子视图展示出来的动画,比如你在桌面移动图标时,你移动一个图标,后面的图标会以动画的形式后移让开一个空。这种动画其实就是基于布局事件与属性动画复合处理而成,并不是新的动画系统。
第四种是 转场动画(Transition Animation),通常用于一个视图到另一个视图的转换。比如A视图渐隐B视图渐现这样,或B视图从边上推进来盖住A视图,其实也是属性动画的复合实现。
第五种就是 视频动画。其实就是播放视频代替动画,这个就不多说了
当然因为本人对游戏编程,对unity3d等技术不熟,还有一些物理引擎动画,通过模拟现实生活中的物理动作实现的动画等,因为我对此不熟,就不介绍了,等我后面学习到再作个补充。
本章主要介绍显示属性动画,这也是Core Animation动画的核心。
1.1 让我们来设计一套属性动画系统
如果让我们来设计一套简单的属性动画系统,根据我们在动画概述里学习到的,主要要有这几个部分组成:
计算
- 对属性的计算,比如某时刻下可动画属性的值;计时
- 对象属性随时间是个怎样的变化;UI刷新
- 属性变化后怎样刷新UI;线程管理
- 当然,总不可以在主线程绘制动画吧,驱动动画的子线程我们还要加以管理。
由此,我们可以作出最简单的动画系统:
我们看下这套系统:
XXLayer
负责UI的显示与重绘,我们假设动画针对layer的一个属性propertyX
,则它的重绘方法会依赖propertyX
绘制出当前的效果;XXAnimationThread
与XXAnimation
负责动画线程管理,我们用XXAnimation
封装一个动画,它包括了一个属性动画需要的基本参数,表示在duration
时间段时,属性值从from
值过渡到to
值。XXAnimationThread
提供线程管理与计时功能,开启动画后,每隔一段时间利用XXAnimation
的参数与计时方法算出属性当前值,并调用XXLayer
的redraw
方法刷新界面;- 而
XXAnimation
中的timingFunction
负责最核心的计算功能,其
我们用伪代码剖析一下最简单动画系统的实现,主要细节在于XXAnimationThread
的startAnimation
方法:
|
|
|
2 转场动画(Transition Animation)
在之前介绍View Animation时我们介绍过过转场动画
。
2.1 图层树的转场动画
CATransition
可以对图层树做动画,这个是非常强大的(个人猜测这个应该是对整个图层树的cache image做动画实现的)。这意味着你可以不用考虑图层树的细节,直接在layer根部使用transition animation
,方便地启用动画。
比如我们下面的操作想在tab切换时展示页面转换的渐隐渐现动画:
|
|
2.2 CAMediaTiming 协议
CAMediaTiming
协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayer
和 CAAnimation
都实现了这个协议,所以时间可以被任意基于一个图层或者一段动画的类控制。
- 一个动画迭代
CAMediaTiming
中定义的一些属性(也可以称之为参数),给要进行的动画的一次迭代指定了时间与计时的参数。比如duration
与repeatCount
这两个参数,duration
表示动画一个迭代的持续时间, repeatCount
表示整个完 大专栏 《iOS三问》 -- 从动画系统的实现谈iOS核心动画整动画包含多少次迭代。,如果duration=2
,repeatCount=3
,就意思着整个动画时长为2*3=6
秒。
duration
与repeatCount
的默认值为0,分别代码0.25秒
与1次
迭代。把repeatCount
设为INFINITY
表示无限迭代;
- timeOffset和beginTime
timeOffset
和beginTime
类似,但是和增加beginTime
导致的延迟动画不同,增加 timeOffset
只是让动画快进到某一点,例如,对于一个持续1秒的动画 来说,设置 timeOffset
为0.5意味着动画将从一半的地方开始。
- 其它参数
speed
是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。如果2.0的速度,那么对于一个 duration
为1的动画,实际上在0.5秒的时候就已经完成了。
此外,还有autoreverse
,会自动生成从to
到from
的反向动画等参数,具体罗列在文章头部的UML图中。
最后值得一说的就是fillMode
,它是一个 NSString 类型,表示当动画播放完后保持在一个什么状态。我们都之前说过,一般来说,动画播放完就被remove掉了。但是如果你设置了removeOnCompletion
为NO
,就会保持在动画的一个状态,而fillMode
就是控制它展示什么状态。其可选属性:
|
|
kCAFillModeRemoved
为默认值,表示动画不再播放时显示图层当前模型中的值。而forwards,backwards,both
表示展示为动画开始时或结束时的状态。这样可以避免动画被中断,或因各种原因在结束时突然地变到一个状态去,要注意的一点就是,需要把 removeOnCompletion
设置为 NO
。
3 引用
【1】[Matt Neuburg - 《Programming iOS deep into views,view controllers and frameworks》]
【2】[Nick LockWood - 《iOS Core Animation: Advanced Techniques》]
CAAnimation
定义了动画的基本属性,在上面的类图中我用空行大致分了段,有+ (instancetype)animation
这样的方便创建动画对象的实例方法,设置动画回调对象delegate
;有与动画计时相关的beginTime
,timeOffset
,duration
等,还可以指定计时方法timingFunction
,以及动画的额外控制autoreverses
、repeatCount
等。另外CAAnimation
实现了KVC
,也就是你可以像字典一样对特定的key赋值,这点后面会说到。
CAPropertyAnimation
更进一步,对动画所对应的动画属性做了定义,keyPath
对应于要产生动画的CALayer动画属性。additive
表示动画结束后的属性值将做为layer的当前值(即把layer的当前呈现值赋给模型值,参考动画基础中的’呈现与模型’小节理解下)。
CABasicAnimation
则是动画继承结构中较常用到的实现类了,使用它可以定义一个具体的基本的动画行为。比如fromValue
、 toValue
表示一个可动画的属性从一个值渐变到另一个值。byValue
表示从动画属性值的变化量,比如fromeValue=0
,byValue=5
,则表示可动画属性从0变化到5. 而如果只设置了byValue
,则表示可动画属性是从属性的当前值变化到当前值+byValue
。到这里你就知道了,使用byValue
, 其实就是让系统帮你计算toValue
,动画的本质还是属性值从fromValue
变化到toValue
。
3.1 使用CABasicAnimation
使用CABasicAnimation时首先要注意下呈现与模型问题。因为我们直接修改layer的值时改的是模型层的值,直到渲染周期来临时执行动画逻辑此时呈现层属性值才会从fromValue
变到toValue
。而动画结果后,动画对象会被干掉,此时呈现层会呈现模型层当前的属性值。也就是如果你修改的layer的值与动画toValue
不一致的话,动画结果后layer会突然变到你改变的值,这样对用户来说很不友好。所以,一般我们需要保证动画结束后的值与当前模型层的值相等。
一种方法是省去fromValue
和toValue
值,因为系统会自动帮你计算。参考动画基础中的’呈现与模型’小节理解下,我们说Layer有呈现层(presentationLayer)
与模型层(moduleLayer)
,当我们改变layer的值时,先是改变其模型层的值,直到下一次渲染周期到来时触发动画才开始变化呈现层的属性值。而这时呈现层的当前值也就是你改变Layer属性值之前的值,也就是动画的fromValue
。而要变化到的值也就是toValue
则是模型层当前的值。如果这样说不好理解可以参考下下面的示例(假设animLayer当前x值为100):
|
|
上面动画开启后, animLayer将会从x=100处移动x=200,然后停在那里。
还有另一种方法是使用addtive
,这样就可以省去在添加动画之前修改CALayer模型层的属性值。因为如上文所述,动画结束后会将呈现层(presentationLayer)
的属性值同步到模型层(moduleLayer)
上。比如同样是实现上面的layer从0移动200处的动画
|