这篇我们来讨论下Core Animation的原理和在iOS开发中的应用.
Core Animation原理
Core Animation是用来展示view或者其他可见元素动画的机制。有了Core Animation(以下简称CA), 开发人员只需要配置一些简单的动画参数就能够创建出流畅的原生动画,例如开始位置,结束位置(basic core animation类)等等. 在Apple的框架中,Core Animation所处位置如下图所示:
可以看到,CA位于UIKit和AppKit下方,算是一个较为底层的框架。CA存在于QuartzCore framework,这里面也包含一些OpenGL和Graphics的子框架。这些框架会将生成的动画、图片等图像信息传给底层的绘图硬件(caching成bitmap),产生最终用户看到的界面。
CA涉及两个部分,一是CALayer, 另一个是Animation. 我们先来讲CALayer。
CALayer
在iOS中,UIView是我们我们最常用的一个类,整个的UIView是建立在CA层上的,每个UIView及其子类都存在一个layer层(root layer)。这个层是干什么的呢?
相信你们或多或少都写过这样的代码吧:
demoLabel.layer.cornerRadius = 5.f;
demoLabel.layer.borderColor = []UIColor grayColor];
demoLabel.layer.borderWidth = 1.f;
代码中的layer就是CALayer。 CALayer是用来管理布局属性的,即用户可见部分,也就是说,我们看到的一个个label, textView,button都是CALayer帮助我们画出的,而UIView,UITextView以及UIButton都是CALayer的一个封装,为其提供了事件响应能力。当然,平常我们写代码时,一些简单的可见属性例如background color是可以用UIView本身来设置的,但是一旦涉及到复杂精细的界面效果, 则非CALayer莫属。
在本篇中,我们不讲述CALayer是如何在iOS和OSX中的布局方式和不同的坐标系统,若您想了解的话,可点击以下两个链接: 有<a href="http://www.jianshu.com/p/c6714dda9a9c">中文版</a>和<a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreAnimation_guide/CoreAnimationBasics/CoreAnimationBasics.html#//apple_ref/doc/uid/TP40004514-CH2-SW3">英文版</a>可供选择,英文是Apple Doc里的,官方内容; 中文版来自简书。在本篇中,我们偏向更理论的部分。
和UIView一样,layer也有树形结构,也有sublayer, superlayer之分。我们可以参考下图:
如图所示,我们可以像添加subview一样添加sublayer,最终产生的效果是一样的。 而且右侧的layer树也和UIView的层级树一样自顶向下。本质上说,UIView的层级树都会转换成layer的树形结构, 交给底层的绘图硬件去处理,性能也因此得到了极大的提升。
CALayer,或者说我们经常说的layer,本质上是一个model tree,它是系统用来存储当前属性的object,例如一些动画的target value, 起始值,结束值等等。但是,在CA中的CALayer可不止一个model tree,还有2个,分别是presentation tree和render tree。它们3个分别反映了动画状态的不同方面。我们之前已经说过model tree用来存储一些静态属性,平常我们的赋值操作就是直接和model tree进行交互。与此同时,presentation和render tree中也生成了和model tree相同的对应树形结构,为将来动画展示做准备。不同的是,render tree是私有的object, 对我们不可见,被用来渲染动画(数据), 其运行的线程在比当前app主线程优先级还高,是在Apple的render server进程中进行的渲染,不会造成主线程堵塞。细心地你当然也会发现这样会增加进程间通讯的开销,所以Apple不建议开发者手动commit transaction而是默认在run loop中提交事务(进程间传递的数据主要有整个render tree和对应的动画)。 三种tree关系如下图所示:
CALayer中还有个比较重要的概念就是它具有层级关系的时间系统, CALayer实现了CAMediaTiming协议,用来控制动画的展示等,例如暂停和恢复。这部分内容我们另起一篇讲.
Animation
众所周知,Apple的对动画的处理真是流畅之极,细腻的也恰到好处。比方说,对于root layer来说,添加动画一般会调用UIViewAnimationWithBlocks这个category中的animateWithDuration系列方法, 使用者只需要在callback中写入想执行动画的代码即可.类似spring这样的具有damping效果的动画也可以这样添加。
但是,这些都是较为简单的动画,而且针对的是root layer。那如果我们的layer是我们自己添加的sublayer该怎么办呢?答案也很简单,添加显式动画。之前提供的那个<a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreAnimation_guide/CoreAnimationBasics/CoreAnimationBasics.html#//apple_ref/doc/uid/TP40004514-CH2-SW3">中文版</a>链接中有不同layer添加动画的demo code, 包括设置各种不同属性等等,大家可以参考。我在这里更多的是探讨下Animation的结构.
显式动画的基类为CAAnimation,常用的是CABasicAnimation, CAKeyframeAnimation有时候还会使用到CAAnimationGroup来做组合动画. 不过我们这次只讲比较重要的CABasicAnimation和CAKeyframeAnimation,其他的有机会再讲。
不管是CABasicAnimation还是CAKeyframeAnimation都是继承于CAPropertyAnimation.
图中有3个属性, fromValue, toValue和interpolated point.这三个都是可选的, 但是不得同时多于2个为非空,一般来说,我们仅指定起始点和终点即可,中间的值由timing function插值计算出来, 默认是线性变换. Timing function是控制插值计算的,形象点说是Timing Function决定了动画运行的节奏(Pacing), 比如是均匀变化(相同时间变化量相同),先快后慢,先慢后快还是先慢再快再慢. 例如我们在给view添加动画时,会指定一些option例如 curveIn, curverOut, flip等等,这些其实实现时都是依靠时间函数来实现。时间函数本质是映射,决定了时间和对应的变化量,例如一秒内透明度的变化. 当使用basicAnimation时, 自定义的timing function可以是一条光滑的贝塞尔曲线, 由起点,终点和2个关键点控制: 如下图:
但是如果动画比较复杂, 既不是直线,也不是贝塞尔曲线, 那么就需要通过CAKeyframeAnimation来实现. 关键帧动画很强大,可以让我们充分的实现我们自定义的动画效果,例如落叶的飘落,纵向翻转(转场动画)等等。当然你可以用嵌套basicAnimation的方式实现(平移+旋转),但是那样既复杂,代码不简洁,而且动画很生硬。但Apple给了我们解决方案---keyFrame Animation!
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
options:(UIViewKeyframeAnimationOptions)options
animations:(void (^)(void))animations
completion:(void (^__nullable)(BOOL finished))completion
+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime
relativeDuration:(double)frameDuration
animations:(void (^)(void))animations
- frameStartTime 表示关键帧动画开始的时刻在整个动画中的百分比
- frameDuration 表示这个关键帧动画占用整个动画时长的百分比
我们需要做的就是在第一个方法中添加关键帧, 设定开始时间和持续时间, 并在animation callback中设置某个keyFrame结束时的view位置,透明度等等, 之后,系统会为我们插入中间帧, 确保动画的平滑。在关键帧动画中还有一个非常重要的参数,那便是calculationMode,计算模式. 他决定插值的方式: 即当在平面座标系中有多个离散的点的时候,可以是离散的,也可以直线相连后进行插值计算,也可以使用圆滑的曲线将他们相连后进行插值计算:
- UIViewKeyframeAnimationOptionCalculationModeLinear // 连续运算模式,线性
- UIViewKeyframeAnimationOptionCalculationModeDiscrete // 离散运算模式,只显示关键帧
- UIViewKeyframeAnimationOptionCalculationModePaced // 均匀执行运算模式,线性
- UIViewKeyframeAnimationOptionCalculationModeCubic // 平滑运算模式
- UIViewKeyframeAnimationOptionCalculationModeCubicPaced // 平滑均匀运算模式
总结
Apple的CoreAnimation由两部分组成: CALayer和Animation. 它们两个分别确保了view的显示和动画的平滑。深刻理解core animation的原理不仅有助于我们调试代码, 理解他们之间的关系,更可以帮助我们创造出优美且性能优越的动画, 提升用户体验.