动画的暂停与恢复原理

之前在做一个语音播放功能的时候,封面随着播放而旋转,而封面的旋转动画会随着音频的播放状态而改变。那么怎么暂停和恢复这个旋转动画呢?对于面向搜索引擎编程的程序员来说,立马在谷歌输入“CAAnimation pause”字样立刻就找到了如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func pause() {
let pausedTime = layer.convertTime(CACurrentMediaTime(), from: nil)
layer.speed = 0
layer.timeOffset = pausedTime
}

func resume() {
let pausedTime = layer.timeOffset
layer.speed = 1
layer.timeOffset = 0
layer.beginTime = 0
let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
layer.beginTime = timeSincePause
}

CMD+R 跑一下,OK,完美运行。

解决了问题之后,那么为什么这段代码能够暂停和恢复动画呢?CACurrentMediaTime()beginTimetimeOffset 又是些什么东西,起什么作用呢?

我们一个一个来看。

根据文档,CACurrentMediaTime() 返回的是 CoreAnimation 的绝对时间,换一种说法就是其返回的是手机自开机以后所经过的秒数。其时间轴的参考系是手机的运行时间而不是现实世界的真实时间。

beginTime 指定了 layer 相对于其父视图的开始时间,默认为0。
文档说得比较抽象,还是要实践出真知。来看下面一段代码:

1
2
3
4
5
let view = UIView()
view.frame = CGRect(x: 10, y: 10, width: 100, height: 100)
view.backgroundColor = UIColor.red
view.layer.beginTime = 2
addSubview(view)

运行一下,可以看到最开始的时候,我们添加的视图是不会显示的,在等待 2 秒之后才会出现。如果把代码改一下:

1
2
3
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
view.layer.beginTime = 2
}

这时发现视图是一开始就绘制出来了,之后也没有消失。
再把代码改一下:

1
2
3
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
view.layer.beginTime = CACurrentMediaTime() + 2
}

此时,可以观察到,视图在一开始被绘制了出来,但是在 5 秒之后视图消失,2 秒之后视图再次出现。

beginTime 实际上是设置的一个时间点,它标记了本对象相对于父对象的启动时间。假如父对象的时间基点是100,第一个例子中就是相对于父对象 2 秒后启动,也就是102秒的时候启动。第二个例子同样是102秒的时候启动,但是此时父对象已经到了105了,时间不可能倒流,所以这样设置没有效果。第三个例子,时间来到105秒,此时设置的启动时间是当前的时间点再加上2秒,也就是107秒的时候启动,所以能能正常运行。

解释有问题
第三个例子中的 beginTime 如果是相对于父对象的话,应该是107 秒后启动,为什么也会是 2 秒后启动呢?

timeOffset 的概念理解起来就容易得多了。它的作用就是将 layer 设置成某一个时刻的固定状态。比如说一个 10 秒的进度动画,在第 2.5 秒的时候是绘制了 90 度,那么将 timeOffset 设置为 2.5,那么 layer 就会呈现出 90 度的样子。
需要注意的是,timeOffset 的时间是layer 的本地时间,写代码的过程中要先计算出本地时间再进行设置。