《iOS 三问》 -- iOS 动画概述
1 Animation 概述
移动 App 内的动画,虽然看起来很炫酷,但其实花样并不多,无非就是可视元素大小变化、位置变化、颜色透明度变化等。在 iOS 中,将动画的本质视为 一个视图的可动画属性随着时间的改变
。
我们现在用计算机的思维去思考一次 app 中的一个动画 – “一个 view 从屏幕的一个位置移动到另一个位置”。
动画就是一个视图的可动画属性随着时间的改变
,在这个动画中,无非就是 view 的位置随着时间而改变,然后在屏幕上实时地刷新其当前位置。我们现在来剖析这个动画,它其实就是 view 从一个位置 positionA,移动到屏幕上另一个位置 positionB
,为了实现这个动画,我们要在 app 界面上自动生成一个小电影 – 每隔一段时间计算 view 当前应该要在的位置,然后发出信号刷新 UI,让 view 显示在新的位置上。如果这个计算与刷新的速度足够快的话,这个动画看起来就是连续的,也就是真是成为一个动画了。
如果我们要自己去实现一个动画的细节,这将是一个浩大的工程,我们要自己去管理动画实现的这些细节:
计算
- 对属性的计算,比如某时刻下可动画属性的值;计时
- 对象属性随时间是个怎样的变化;UI 刷新
- 属性变化后怎样刷新 UI;线程管理
- 当然,总不可以在主线程绘制动画吧,驱动动画的子线程我们还要加以管理。
幸运的是,iOS 早已想我们所想,为我们提供了一系列方便的框架及 API。这样就使得我们不需要花费精力在动画的具体实现上,而可以把精力集中放在动画的表现上。
2 动画是怎样实现的
上面我所说的是计算机中一般动画的实现,那么具体到 iOS 中,动画是怎样实现的呢?
2.1 重绘时机 (redraw monment)
在搞清楚动画细节前,我们需要了解一下一个很重要的概念 – 重绘 (redraw)。
之前我们介绍 view 与 layer 时,介绍过 UI 的绘制原理与重绘原理,但是,当 view 或 layer 属性发生改变,会在什么时候触发重绘机制来刷新 UI 显示呢?
我们说,当我们改变一个 View 的可动画属性时,UI 的变化并不是马上呈现到屏幕中的。事实上系统会把这个修改记下来,然后对这个 View 进行一次标记,标记它即将重绘 (也就是我们之前介绍过的 setNeedsDisplay
做的事情)。如果有多个修改,系统会将之构造在一起,在你这段代码运行结束时或系统空闲时对屏幕进行重绘 (这一重绘时刻,也称之为 redraw monment
)。
所以,我们可以说,重绘时机主要在下面两个:
- 当 view 可动画属性修改时,触发重绘
- 手动调用 setNeedsDisplay
这解释了下面的语句为什么不会造成 view 闪烁,因为在真正发生重绘时,只有最后一个修改会最终生效,绘制于屏幕上。
1 | view.backgroundColor = UIColor.redColor; |
2.2 动画发生的时机
与重绘一样,当你想开始一段动画时,也得等到重绘时刻 (redraw monment) 动画才会真正开始。
当你把 view 从 position1 移动 position2 时,会发生如下事情 :
- View 的 center 这个可视的位置属性,被设置成 position2;但是还没有到重绘时刻,所以 view 现在看起来还在 position1 位置;
- 设置完 position 属性后的代码继续运行;
- 重绘时刻终于来临,如果没有动画的话,view 会马上被重绘到 position2 的位置。如果有动画,则会开始 view 从 position1 到 position2 的动画。动画结束,把 view 放在 position2 位置上。
- 如果有动画的话,动画播完后会被移除,现在 view 的属性与看起来的位置都在 position2 上。
做动画时,要确保的一点就是,动画放完之后,view 当前的属性与动画结束时的属性应该是一致的。
2.3 呈现与模型
动画是在一个独立的线程中发生的。
动画不仅使用了多线程技术,还使用了 “多图层技术”。我们平时看到的 layer 其实不是 layer 本身,是它的presentation layer,你可以通过 presentationLayer这个属性获得它;
当你通过修改一个 view 或 layer 的可动画属性来开始一个动画时,layer 的这个可动画属性也跟着立马就变了,但是它的presentation layer却没变。直到我们所说的重绘时刻发生时,presentation layer才会随着时间改变,这就形成了动画!
独立线程还有个好处就是,在动画的整个生命周期,可以对动画的不同阶段进行回调。
而且如果这个线程被打断也不要紧,因为动画线程只是用于播放动画,实际上动画被打断,view 的相关属性早就已设置成最终的值,界面恢复时 view 也会出现在它所要在的正确位置。
2.3.1 呈现与模型的设计思想
这里值得再说一说,我们说 CALayer 的设计其实使用了典型的 MVC 模式
。CALayer 是一个连接用户界面的模型类,存储了视图应该如何显示和动画的数据模型,而具体的展现,用的是 view 视图 – presentation layer
。
当我们修改一个视图的可视属性,数据模型 CALayer
的属性会马上更改,而视图层 presentation layer
的属性不会马上变化,而是会根据 redraw monment
时的状态再更新其显示或由动画线程动态地改变。而如果你要从视图层 presentation layer
获取当前视图真正的属性值的话,可以通过其 modelLayer
属性,其实就是指回了其数据模型 CALayer
。
2.3.2 什么时候会用到 presentation layer
当你要自己实现基于自己的定时器的动画时,可能要用到
presentation layer
去自行地改变视图层当前的可视状态。当你想在图层动画过程中响应用户手势操作时,也有可能会用动。因为当前图层的真正响应区域其实是动画结束时的区域,如果你想在图层动画之中的位置响应用户手势的话,就得用
presentation layer
去判断hitTest
等。
3 小结
通过这些分析,我们对动画实现的细节是不是加深了了解呢,动画的实质是 一个视图的可动画属性随着时间的改变
。当可动画属性改变后,差不会马上进行重绘或动画,而是等在重绘时刻中进行。在动画进行时,会在自己的 独立线程
里计算该属性在当前时刻的插值,然后通过更新 presentation layer
来显示动画。
4 我们的收获
4.1 UI 系统中动画的实现与 iOS 中的优化
其实,在不同的 UI 系统中,如 Windows、Android 等也和 iOS 的动画实现一样,基本都是使用 一个视图的可动画属性随着时间的改变
这样的思想去设计的。架构上也差不多都是有个动画的独立线程,然后在线程里对动画进行插值计算,异步 UI 刷新等工作来实现动画。而 iOS 对这个基本的动画框架做了上述的一些优化:
- redraw monment
比如 redraw monment
的设计,iOS 并不是每次修改可动画属性都触发 UI 的重绘,而是在属性的修改都进行完毕后在一个时机统一来进行重绘或动画,从而减少重绘的次数。而且这个重绘是自动进行的,比某些 UI 框架修改完后必须手动调用或发消息触发 UI 刷新要更简单和方便。
- 动画生命周期的回调
动画生拿周期的回调也让 API 使用者用起来更加的方便了,比如你可以在动画开始时禁止用户对界面的操作,然后在动画结束回调中打开界面的可操作,从而保证你的这次动画不受用户操作的影响,等等,这让使用者使用动画与实现逻辑可以更加紧密和方便了。
4.2 呈现与模型的设计思想
当我们设计 UI 组件时,可以学习此设计思想,将组件的业务与显示分开。这样做可以降低控件实现逻辑与展示逻辑之间的耦合,特别在实现复杂功能的 UI 组件时,会让整个设计结构更加清晰,功能边界明了。
5 引用
【1】[Matt Neuburg - 《Programming iOS deep into views,view controllers and frameworks》]
【2】[Nick LockWood - 《iOS Core Animation: Advanced Techniques》]