这是我第一次翻译国外大神的文章。为了行文通顺,某些地方没有完全遵照原文。末尾附有自己的一些私货。
原文链接如下:
http://ronnqvi.st/controlling-animation-timing/
如有疏漏,敬请指出,不胜感激。
概述
在iOS的动画框架中,有一个叫做CAMediaTiming的协议,它由CAAnimation实现,而后者是CABasicAnimation和CAKeyframeAnimation的基类。所有和时序相关的属性:duration, beginTime, repeatCount等等都是从这个协议中来的。总体来说,该协议定义了8个属性,这8个属性可以通过不同的组合方式来精确控制动画时序。苹果官方文档对每个属性的介绍都非常简略,因此阅读官文会比阅读这篇博客更快,但是我个人认为,有关时序的问题,最好还是结合图表来理解。因此花费一些篇幅是有必要的。
图解CAMediaTiming
为了解释和时序有关的各个属性,我现在要做一个颜色变化的动画(橙->蓝)。下图中的格子表示一段动画从开始到结束的整个时间线,而其中每一格代表时间线中的一秒。你可以看到整个动画过程中任意时刻的颜色。
duration
下图展示了设置duration=1.5时的情景。
CAAnimation会在动画结束后默认从layer上移除,该过程同样如图上所示。一旦animation对象到达了它的“最终值”(animation的值通过“插值”法进行变化。关于“最终值”的解释会附在文末。——译者注),它就会从layer上移除。如果layer原本的颜色是橙色(注意,不是说它的fromValue是orangeColor——译者注),那么在动画结束后layer将变回橙色。在该图示中,layer的原本颜色是白色,所以在动画添加到layer上1.5秒后,layer就会变成白色。
beginTime
如果我们再加上beginTime这个属性会更好理解一些。如图:
duration被设置为1.5秒,beginTime被设置为currentTime(CACurrentMediaTime() + 1),所以动画将在Animation对象被添加到layer上1秒后动画开始执行。2.5秒后结束。
如果要让layer在动画开始前显示Animation的fromValue,你可以设置fillMode为kCAFillModeBackwards,来让动画“向后填充”。图示如下:
autoreverses
autoreverses属性可以让动画从“起始值”执行到“最终值”,然后再反过来,从“终止值”执行到“起始值”。因此,整个动画执行时间将会是duration 的2倍。
repeatCount
repeatCount可以将动画重复执行2次(如下图所示)甚至任意次(你可以设置repeatCount为1.5,让动画执行一次半)。一旦动画执行到了“最终值”它会立即回到“起始值”然后重新开始执行。请读者将autoreverses和repeatCount属性做一个对比。
repeatDuration
repeatDuration作用和repeatCount类似,但是却很少用到。它会单纯地在给定的时间内(尽可能地)重复动画。下图所示为repeatCount=2时的情景。如果repeatDuration小于duration,则动画会提前结束(即动画的真正执行时间取决于repeatCount。——译者注)。
以上属性都可以组合使用。
speed
speed是一个更为有趣的时序属性。如果duration=3,speed=2,则动画执行时间会是1.5秒。(即执行时间=speed * duration)。
如果只是为了控制一个简单动画的速度,你当然可以通过设置beginTime和duration来达到目的。但是speed属性的强大之处在于:
- 动画的速度具备“等级”关系。
- CAAnimation不是唯一一个实现了CAMediaTiming的类。
速度的“等级关系”
如果一个动画组(Animation Group)的speed为2,而其中一个动画的speed为1.5,那么该动画将会以三倍的速度播放。
CAMediaTiming的其他应用
CAMediaTiming不仅被CAAnimation类实现,也同样被CALayer实现。后者是所有核心动画图层的基类。因此,对CALayer的speed赋值将会影响其上面添加的所有动画。最终speed = layer.speed * animation.speed。
动画暂停
通过设置speed属性为0,我们可以暂停一个动画。timeOffset属性为我们提供了一个可以从外部控制动画进程的机制。例如,可以通过slider来查看动画的每一个时刻的样子。
timeOffset属性乍一看会很奇怪。顾名思义,该属性用于抵消计算动画状态的时间。我们最好通过图画来解释。下图表示一个duration=3,timeOffset=1的动画。
该动画直接调到“橙色->蓝色”这个动画过程的第1秒处开始执行(此时它已经不是纯橙色了——译者注),直到2秒时完全变为蓝色。随后,动画跳转到其纯橙色然后执行其第0秒第1秒的动画过程。也就是说,timeOffset将动画01秒的过程抽取了出来,放到最后执行。
这个属性本身没什么用处,但如果同时把speed设置为0,我们就可以控制动画的“当前状态”。被暂停的动画将会卡在第一帧上,如果你看上图所示的带偏移量的动画最开始的颜色,你会发现它其实是颜色变化开始1秒后应该呈现的颜色。通过将timeOffset设置为其他值,你可以看到动画该时刻的样子。
控制动画时序
speed和timeOffset结合使用可以控制动画的“当前”时间。如下所示是一个slider的实例,我们通过设置其timeOffset来查看方块的颜色变化。
Slider
这个例子非常简单,创建一个basic animation,加载到一个layer上。我们把动画的speed设置为0,让它停下。
CABasicAnimation *changeColor =
[CABasicAnimation animationWithKeyPath:@"backgroundColor"];
changeColor.fromValue = (id)[UIColor orangeColor].CGColor;
changeColor.toValue = (id)[UIColor blueColor].CGColor;
changeColor.duration = 1.0; // For convenience
[self.myLayer addAnimation:changeColor
forKey:@"Change color"];
self.myLayer.speed = 0.0; // Pause the animation
然后在action方法中我们将slider的value赋值给做动画的layer的timeOffset属性:
- (IBAction)sliderChanged:(UISlider *)sender {
self.myLayer.timeOffset = sender.value; // Update "current time"
}
效果图:
小结
- 通过设置fillMode为forward,并将removedOnCompletion设为NO,可以让图层保持在动画结束以后的状态。但是注意,animation对象只会影响到图层的PresentationLayer,而对ModelLayer没有影响。
- 如果对speed属性赋负值,动画会倒着执行。
私货
关于timeOffset
苹果官方文档的解释:
Specifies an additional time offset in active local time.
翻译过来就是“在本地活跃时间中指定一个额外的时间偏移量”。
听起来还是很费解是吧?其实timeOffset就是说,把动画时序中开头的某个时间段分割出来,“拼接”到动画的末尾。由于这个操作改变了动画真正的起点,因此,动画看起来就从分割处开始执行了。
animation的插值法
对于动画的“值”,苹果给出了三个属性,分别是fromValue, toValue, byValue。苹果规定,最多可以同时给这三个属性中的两个赋值。运行时,动画在“初始值”和“最终值”之间进行插值,从而确定各个时刻Layer的状态。初始值不一定是fromValue哦!
插值策略如下:
- 赋值给fromValue和toValue:在fromValue、toValue之间插值。
- 赋值给fromValue和byValue:在fromValue、fromValue + byValue之间插值。
- 赋值给B和toValue:在toValue-byValue、toValue之间插值。
- 赋值给fromValue:在fromValue和该属性当前的value之间插值。
- 赋值给toValue:在keypath属性的presentationlayer的当前值和toValue之间插值。
- 赋值给byValue:在keypath属性的presentationlayer的当前值和byValue之间插值。
- 所有的都为nil:在keypath属性的presentationlayer先前的值和现在的值之间插值。
对于最后一条,我自己也没有尝试成功过。不知是不是个人理解出了偏差。这里先附上官文原文:
All properties are nil. Interpolates between the previous value of keyPath in the target layer’s presentation layer and the current value of keyPath in the target layer’s presentation layer.
欢迎各位大神指点!