《iOS 三问》 -- iOS 动画之 CALayer 隐式动画
1 什么是隐式动画
iOS App 为什么能那么惊艳,一经出炉就靓瞎大家的眼睛,直接把 web 服务完爆之,靠的就是其流畅优美的动画。而实质上,我们 App 中的大部分动画都由系统帮我们默默实现的。比如页面 push、pop 时的转场动画,弹出对话框、打开 App 等的弹入弹出效果等,iOS 框架在底层帮我们做了很多事情使得 开发者可以花最少的精力给 App 做出动效,从而保证了每个 App 体验上的一致地优美
– 而 隐式动画
无疑是这个思想下最优雅的产物。
试想下,你只要改变一个 view 的背景色,系统就自动帮你生成 view 背景色的渐变效果,而无需写各种烦人的动画事务、动画参数配置等等。这样做还有个好处,就是让你的 App 不仅更美观更炫,而且能让变化之处引起用户的注意。你想想看,在一个这么小的屏幕下界面往往充斥着各种各样拥挤的信息,而改变处的一则动画,能够更吸引用户的注意,从而不会错过你想提醒用户要注意的事情!
1.1 隐式动画与非 RootLayer
我们来看一处简单的隐式动画,下面的代码中,系统将自动生成隐式动画:从黄色到红色渐变,并向右位置 200 个象素点位置
1 | //view frame 作为动画元素 CALayer 的载体 |
但是并不是每个 layer 都有隐式动画,只有 非 Root layer
才可以。每一个 UIView 内部都默认关联着一个 CALayer,我们可用称这个 Layer 为 Root Layer(根层)。所有的非 Root Layer,也就是手动创建的 CALayer 对象,都存在着隐式动画。
- 为什么要非 RootLayer 才有隐式动画?
对于喜欢深究的童鞋一定不会满足教条式的传授,对啊,为什么只有非 RootLayer 才有隐式动画呢?这点当我们介绍完 动画事务
及 重绘时刻的本质
之后,就可以揭晓了。
1.2 可动画属性
那些会产生隐式动画的属性称为 Animatable Properties (可动画属性)。CALayer 支持的可动画属性在文章最上层的导图可以看到。
这里主要要注意的就是 frame
并不是可动画属性!!当你要对 layer 的位置或大小作隐式动画时,应该使用 bounds
或 position
。
2 动画事务与隐式动画的实现
CATransaction,动画事务,是 CoreAnimation 框架动画实现的重要角色,负责把一系列动画请求组合成一个独立的动画过程,用其 begin
及 commit
方法包住 – 这一系列打包的动画组合我们称之为 事务 block(transaction block)
,这一切也可以使用显示动画手动编写,但是在 CA 框架为我们的代码自动生成事务 block 而不用手动写 begin
及 commit
,从而把我们对可动画属性的修改包装成一个动画事务。
2.1 begin
及 commit
方法
通过上面的分析,我们了解到一个动画事务就像是一段封装好的代码,类似这样:
1 | [CATransaction begin]; |
实际上 CATransaction 的 begin
及 commit
方法其实是一个事务栈操作。begin 是入栈一个动画事务,而 commit 则是将栈顶事务出栈。那么隐式动画是怎么产生的呢?那就简单了,只要把可以做动画的图层属性都添加到栈顶的事务就行了。
2.2 重绘时刻的本质
实际上我们所说的重绘时刻,除了用户显式地在代码里调用 setNeedsDisplay
之外,Core Animation 在每个 run loop
周期中自动开始一次新的动画事务(run loop 是 iOS 负责收集用户输入,处理定时器或者网络事件并且重新绘制屏幕的事件循环),即使你不显式的用 [CATransaction begin] 开始一次事务,任何在一次 run loop 循环中属性的改变都会被集中起来,然后做一次 0.25 秒的动画 (也就是说这个默认的 CATransaction 动画的 duration 默认是 0.25)。
而在这个重绘时刻里屏幕更新 UI 具体会做什么呢:
- Layout,对屏幕中的 View 递归地布局 (也就是计算 view 们的位置及大小);
- 重绘 (ReDraw),调用 view 及 layer 的重绘方法重绘自己;
- 修改 view 的 layout 属性;
- 开始动画;
- 动画在子线程里进行,结束后调用 complete block;
2.3 设置当前动画事务
明白了隐式动画的本质是默认的动画事务之后,我们就可以对该动画事务进行配置,从而改变隐式动画的默认效果了。比较常用的有几个方法:
1 | - setAnimationDuration :设置隐式动画的持续时间。 |
比如
1 | [CATransaction setAnimationDuration:2]; |
如果你不想对其它动画事务产生副作用,也可以显示地起一个新的事务
1 | [CATransaction begin]; // 新入栈一个事务 |
2.3.1 关闭隐式动画
如果你想关闭隐式动画,很简单,在修改动画属性前调用 [CATransaction setDisableActions:YES];
即可。
同样,如果你不想产生副作用,可以显示地起一个新动画事务。
2.4 为什么 root layer 不支持隐式动画
现在我们可以来解释一下为什么 root layer 不支持隐式动画了。原来就是UIView 把它的 RootLayer 的隐式动画给关闭了!因为每个 UIView 必有一个 rootLayer,我猜想 UIView 之所以这样设计,默认关闭 rootLayer 的隐式动画,是为了防止滥用隐式动画,以及过度使用之而带来的效率问题。
3 引用
【1】[Matt Neuburg - 《Programming iOS deep into views,view controllers and frameworks》]
【2】[Nick LockWood - 《iOS Core Animation: Advanced Techniques》]