iOS-核心动画时序控制(译)

这是我第一次翻译国外大神的文章。为了行文通顺,某些地方没有完全遵照原文。末尾附有自己的一些私货。
原文链接如下:
http://ronnqvi.st/controlling-animation-timing/
如有疏漏,敬请指出,不胜感激。

概述

在iOS的动画框架中,有一个叫做CAMediaTiming的协议,它由CAAnimation实现,而后者是CABasicAnimation和CAKeyframeAnimation的基类。所有和时序相关的属性:duration, beginTime, repeatCount等等都是从这个协议中来的。总体来说,该协议定义了8个属性,这8个属性可以通过不同的组合方式来精确控制动画时序。苹果官方文档对每个属性的介绍都非常简略,因此阅读官文会比阅读这篇博客更快,但是我个人认为,有关时序的问题,最好还是结合图表来理解。因此花费一些篇幅是有必要的。

图解CAMediaTiming

为了解释和时序有关的各个属性,我现在要做一个颜色变化的动画(橙->蓝)。下图中的格子表示一段动画从开始到结束的整个时间线,而其中每一格代表时间线中的一秒。你可以看到整个动画过程中任意时刻的颜色。

duration

下图展示了设置duration=1.5时的情景。

iOS-核心动画时序控制(译)_第1张图片
WX20170317-134617.png

CAAnimation会在动画结束后默认从layer上移除,该过程同样如图上所示。一旦animation对象到达了它的“最终值”(animation的值通过“插值”法进行变化。关于“最终值”的解释会附在文末。——译者注),它就会从layer上移除。如果layer原本的颜色是橙色(注意,不是说它的fromValue是orangeColor——译者注),那么在动画结束后layer将变回橙色。在该图示中,layer的原本颜色是白色,所以在动画添加到layer上1.5秒后,layer就会变成白色。

beginTime

如果我们再加上beginTime这个属性会更好理解一些。如图:

iOS-核心动画时序控制(译)_第2张图片
图2.png

duration被设置为1.5秒,beginTime被设置为currentTime(CACurrentMediaTime() + 1),所以动画将在Animation对象被添加到layer上1秒后动画开始执行。2.5秒后结束。
如果要让layer在动画开始前显示Animation的fromValue,你可以设置fillMode为kCAFillModeBackwards,来让动画“向后填充”。图示如下:

iOS-核心动画时序控制(译)_第3张图片
图3.png

autoreverses

autoreverses属性可以让动画从“起始值”执行到“最终值”,然后再反过来,从“终止值”执行到“起始值”。因此,整个动画执行时间将会是duration 的2倍。

iOS-核心动画时序控制(译)_第4张图片
图4.png

repeatCount

repeatCount可以将动画重复执行2次(如下图所示)甚至任意次(你可以设置repeatCount为1.5,让动画执行一次半)。一旦动画执行到了“最终值”它会立即回到“起始值”然后重新开始执行。请读者将autoreverses和repeatCount属性做一个对比。

iOS-核心动画时序控制(译)_第5张图片
图5.png

repeatDuration

repeatDuration作用和repeatCount类似,但是却很少用到。它会单纯地在给定的时间内(尽可能地)重复动画。下图所示为repeatCount=2时的情景。如果repeatDuration小于duration,则动画会提前结束(即动画的真正执行时间取决于repeatCount。——译者注)。

iOS-核心动画时序控制(译)_第6张图片
图6.png

以上属性都可以组合使用。

iOS-核心动画时序控制(译)_第7张图片
图7.png

speed

speed是一个更为有趣的时序属性。如果duration=3,speed=2,则动画执行时间会是1.5秒。(即执行时间=speed * duration)。

iOS-核心动画时序控制(译)_第8张图片
图8.png

如果只是为了控制一个简单动画的速度,你当然可以通过设置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的动画。

iOS-核心动画时序控制(译)_第9张图片
图9.png

该动画直接调到“橙色->蓝色”这个动画过程的第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"
}

效果图:

iOS-核心动画时序控制(译)_第10张图片
动图.gif

小结

  1. 通过设置fillMode为forward,并将removedOnCompletion设为NO,可以让图层保持在动画结束以后的状态。但是注意,animation对象只会影响到图层的PresentationLayer,而对ModelLayer没有影响。
  2. 如果对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.

欢迎各位大神指点!

你可能感兴趣的:(iOS-核心动画时序控制(译))