一、引言
在iOS开发中使用动画时,可以通过设置动画的duration
、speed
、begintime
、offset
属性,来设置动画的时长、速度、起始时间及起始偏移。
用一个简单的例子来说明各个参数的的作用。动画很简单,一个红色的方块从左移到右边。动画的持续时间是1s,没有重复,效果如下。
CFTimeInterval currentTime = CACurrentMediaTime();
CFTimeInterval currentTimeInLayer = [self.testLayer convertTime:currentTime fromLayer:nil];
CFTimeInterval addTime = currentTimeInLayer;
anim.beginTime = 0.3 + addTime;
[anim setTimeOffset:0.5];
[anim setSpeed:2];
做修改以后,效果如下:
与上面相比,三处不同
- 动画的速度是原来的两倍。
- 点击开始动画的按钮,到开始动画,有一个延迟。
- 动画起始时,滑块的位置为中央,而不是在左边。
我们已经看到了这些属性的效果。翻阅文档,发现begintime
、speed
等属性是CAMediaTiming
这协议的属性,并且CALayer
、CAAnimation
都遵守了CAMediaTiming
协议。
那么CAMediaTiming
协议是什么呢?有什么作用呢?
二、层级时间结构
根据文档,CMediaTiming
协议构建了一个层级的时间系统,并用这个层级的时间系统来协调各个layer、animation的时间。
这个协议被CAAnimation
及CALayer
遵守,每一个遵守协议的的object对应一个time space
。根据object之间的关系,不同的time space
有层级关系。比如Layer A有一个subLayer B,那么Layer A对应的time space
就是layer B对应的time space
的parent time space
。每一个time space
中时间的数值都是根据parent time space
的数值,以及begintime
、speed
等属性,根据一定的规则来计算的。
为了便于理解层级时间系统,先看下layer在屏幕上的显示位置是如何确定的,然后做一个类比。
layer层级如上。要确定sublayer1在屏幕上的显示位置,一共分三步。
- 确定window layer在屏幕位置position1
- 根据position1及view layer的position属性,确定view layer在屏幕中的位置position2
- 根据position2及sublayer1的position属性,确定sublayer1在屏幕中的位置position3
与此类似,要确定sub1ayer1中的time,也要分三步。
- 确定window layer中的time1
- 根据time1及view layer的
begintime
、offset
等属性计算出view layer中的time2 - 根据time2及sublayer1的
begintime
、offset
等属性计算出sublayer中的time3
和确定layer的位置相比,确定时间有一些复杂,主要提现在下面两点
- 层级时间系统的构成复杂。
layer tree的每一级都是CALayer,而只要遵守CAMediaTiming
协议,就可以作为层级时间系统的一部分。比如CALayer
、CAAnimation
(及其子类CAAnimationGroup
)都可以作为层级时间系统的一部分。 - 不同层级之间时间转换规则复杂
计算当前layer的位置时,只需要知道父layer的位置,以及当前layer的position属性。计算当前层级时间时,不仅需要知道上一个层级的时间,还需要知道当前层级的begintime
、offset
、speed
等属性。转换的规则也比较复杂,要经历两次转换。从parent time
到active local time
,再到basic local time
。
三、active local time
这次转换是为了处理当前层级的object在父层级的的时间线上的位置,以及当前层级和父层级之间时间流逝速度的关系。
和这次转换相关的属性有beginTime
、speed
以及timeOffset
-
begin time
子层级相对于父层级的起始时间。也就是父层级的时间经过多久,子层级才开始计算时间。 比如子层级A被加入层级时间系统时,它父层级B的时间是5s,子层级A的begintime
是6s,那么当它父层级的时间变为6s时,子层级才开始计算时间。 -
speed
子层级相对于父层级的时间流逝速度。如果speed是2,那么当父层级的时间增加了10s时,子层级的时间增加了20s(10s的2倍)。 -
timeOffset
为本地时间增加一个偏移。 如果timeOffset
是5s,那么本地时间的起始就是5s。
从parent time
到active local time
有一个公式,可以用来参考。
t = (tp - begin) * speed + offset
四、basic local time
这次转换是为了处理当前层级的重放(repeat)、以及重放之前是否要倒放(play backward)等操作。
比如当前层级是一个动画(CAAnimation
遵守CAMediaTiming
协议),duration
是1s,经过第一次转换之后的active local time
是5.5s。如果动画的repeatCount
是10,那么经过第二次转化以后,basic local time
会是0.5s,因此当前是动画展示一半的状态。
-
repeatCount
及repeatDuration
当前的层级要重复的次数或重复的时间,两者不可同时指定。 以动画为例,如果指定repeatCount
,那么指定了动画要重复几次。如果指定了repeatDuration
,那么指定了动画重复的时间。 -
autoreverses
在重复之前是否要倒放。
五、文首的例子
根据这些知识,可以解释文章开始时设置参数的效果。
当动画被加到layer上时,动画对应的time space
被加到层级时间系统中,是layer对应的time space
的子层级。
- 动画的速度是原来的两倍
设置动画的speed
是2,这样子动画中的时间流逝速度时layer中时间流逝速度的2倍。当layer中时间经过0.5s时,动画中时间已经流逝了1s,动画已经完成了。(动画的duration
是1s) - 点击开始动画的按钮,到开始动画,有一个延迟
我们首先得到了当前layer的时间addtime
,然后把动画的begintime
设置为addtime+0.3
。这样子当动画被加到layer之后0.3s,layer中的时间是addtime+0.3
,此时动画中的时间才开始计算,之前动画没有开始。 - 动画起始时,滑块的位置为中央,而不是在左边
我们设置了动画的offset
为0.5s。当动画开始时,动画对应的time space
的时间是0.5s,对应动画duration
的一半,即滑块位置在屏幕中央。
六、更多应用
了解了CAMediaTiming
协议后,可以实现很多动画的效果。
- 让某一个layer上的动画停止
设置layer的speed
为0即可。 - 实现门打开然后关闭的效果
实现一个门打开的动画,然后把动画的autoreverses
属性设置为YES
即可。 - layer上的若干动画依次延迟启动
分别设置这些动画的beginTime
为不同的值即可 - 手动控制动画的进度
设置动画的speed
为0,然后改变动画的offset
即可。
苹果已经把工具给我们了,可以做出什么样的产品就看大家的想象力了。
参考
控制动画时间控制动画时间(上文的中文版)Time Warp in Animation