一、概述
在动画中,我们会指定动画的持续时间。例如
scaleAnimation.duration = self.config.appearDuration
那么这个时间是怎么定义的呢?是指的绝对时间吗?
二、层级时间结构
layer在屏幕上的显示位置是根据父layer的位置以及本身相对于父layer偏移定义的。
与此类似,每一个layer都有自己的time space,计算本地时间(local time)时候,需要根据父layer的时间以及一定的转换规则来计算出本地时间。
这个规则就是CAMediaTiming协议。每一个CALayer和CAAnimation实现了这个协议。

三、关于时间的概念
- 绝对时间
Absolute time
由CACurrentMediaTime函数返回,实际调用mach_absolute_time()。 -
active local time
根据CAMediaTiming协议计算得到的当前对象上的时间。 -
basic local time
由于动画可以重复(repeat)或者回放(play backwards)。需要把active local time转化为做动画相关的时间。
例如active local time是5.5s,动画的重复次数是10,动画持续时间是1s。那么5.5s的active local time对应的local time是0.5s。
四、CAMediaTiming协议
-
beginTimeRequired. Specifies the begin time of the receiver in relation to its parent object, if applicable.
指定了指定了父对象时间和子对象时间的偏移。
-
speedSpecifies how time is mapped to receiver’s time space from the parent time space
对动画以及子动画速度应用一个缩放的因子。如果speed是2.0,那么本地时间流逝的速度是父对象的时间流逝速度的两倍。
-
timeOffsetRequired. Specifies an additional time offset in active local time.
对本地时间做了一个偏移。
五、时间转换公式
- 从父layer转化为active local time
\[ t= max\left\{(t_p-begintime),0\right\}\times speed+offset \]
其中\(t\)是本地时间,\(t_p\)是父layer的时间,其他都是
CAMediaTiming要求实现的字段。
六、例子
用一个简单的例子来说明各个参数的影响。动画很简单,一个红色的方块从左移到右边。动画的持续时间是1s,没有重复。
- 设置speed为2,begin time为0.3s,offset为0.5s,效果如下

与上面相比,三处不同- 动画的速度是原来的两倍,这是因为动画的speed是2。
- 动画起始时,滑块的位置为中央,而不是在左边。这是因为offset为0.5s。由于动画的持续时间是1s,0.5s时,动画刚刚进行了一半,滑块的位置是在屏幕中央。
- 点击开始动画的按钮,到开始动画,有一个延迟,这是因为begin time的时间不是默认值,而是有一个0.3s的延迟。
-
时间变换的图像表示
- 从父layer的时间到子layer的active local time

图中,直线的斜率是speed,第一个y值不为零的点,对应的横轴坐标是begintime,对应的y轴坐标是offset - 从active local time到basic local time

图中,不为0的部分的x轴长度,即是动画时间,由repeattime或repeatDuration指定。由于这个动画没有repeattime或repeatDuration,因此就是动画的duration。如果指定了动画的时间,比如repeatcount为3,那么非0部分会重复3次。
如果指定了autoreverses为yes,那么折线会部分有负的曲率。
第一个不为0的点对应的横轴坐标即为offset。fillMode可以理解为不在动画时间内的y值是什么。如果kCAFillModeBackwards,对应于横轴在offset之前时,纵轴对应于offset。kCAFillModeForwards对应于横轴在动画结束之后,纵轴保存不变。 - 从父layer的时间到子layer的active local time
-
父对象和子对象联动
我们的例子中,动画是加在layer上的,它们都遵守CAMediatiming协议,就CAMediatiming看来,动画的父对象是layer。- 设置父对象的speed
我们设置layer的speed为2,动画的speed为0.1,实际的速度会是0.2.
- 设置父对象的offset
设置父对象的offset为0.5,那么动画将会在一半处开始。

- 设置父对象的speed
七、关于begintime
根据公式
\[
t= max\left\{(t_p-begintime),0\right\}\times speed+offset
\]
这里begintime是应该怎么指定呢?如果想把一个加到layer的动画的延迟5s执行,应该把begintime直接设为5吗?
由于begintime是相对于父对象(layer)的时间偏移。由于layer可能在很久以前就存在了,因此对于动画来说\(t_p\)是一个很大的值。直接把begintime指定为5s,那么\(t\)将会是一个很大的值。正确的做法是把begintime设置为5s+这个layer被加到父layer以后,度过的时间,称为addtime.
animation.beginTime = addTime + delay;
如何得到addtime
addTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
参考
控制动画时间
控制动画时间
Time Warp in Animation