原文:Controlling Animation Timinggithub
感谢翻译小组成员@answer-huang(博客)热心翻译。本篇文章是咱们每周推荐优秀国外的技术类文章的其中一篇。若是您有不错的原创或译文,欢迎提交给咱们,更欢迎其余朋友加入咱们的翻译小组(联系qq:2408167315)。app
有一种经过CAAnimation实现的协议叫作CAMediaTiming,也就是CABasicAnimation和CAKeyframeAnimation的基类(指CAAnimation)。像duration,beginTime和repeatCount这些时间相关的属性都在这个类中。大致而言,协议中定义了8个属性,这些属性经过一些方式结合在一块儿,准确的控制着时间。文档中每一个属性只有几句话,因此颇有可能在看这篇文章以前你都已经读过了,可是我以为使用可视化的图形能更好的解释时间。
可视化的CAMediaTiming
为了显示相关属性的不一样时间,不管是他们本身仍是混合状态,我都会动态的将橙色变为蓝色。下面的块状显示了从开始到结束的动画过程,时间线上每个标志表明一秒钟。你能够看到时间线上的任意一点,当前颜色即表示动画中的当前时间。好比,duration像下面同样可视。
duration设置为1.5秒,因此动画全程花费了1.5秒变为蓝色。

Figure 1.设置duration为1.5秒
一旦动画完成后,CAAnimation默认从layer上移除。这从上面的图形中也表现出来了。一旦动画达到最终值,它会从layer上移除。若是layer的颜色是橙色(开始的颜色),那么颜色又会便会橙色。在这个可视化界面中,layer的颜色是白色,因此你也能够看到动画加入layer后的两秒钟又会变白,由于那是动画已经结束了。
咱们也形象的描述一下动画的beginTime,这样让人更容易理解。

Figure 2.设置duration为1.5秒,开始时间为1.0秒
durations设置为1.5秒了,开始时间设为当前时间。(CACurrentMediaTime())加一秒,因此动画在2.5s后结束。动画加入到layer上以后,花费一秒钟时间来启动并呈现出来。结果是1+1.5=2.5
为了让动画从fromValue开始显示,你能够将动画设置为fill backwards。咱们能够经过设置fillMode为kCAFillModeBackwards。

Figure 3.Fill mode可让动画从fromValue开始显示
autoreverses属性能够产生从初始值到最终值,并反过来回到初始值的动画。这意味着动画发生了两次。

Figure 4.Autoreverses使得动画结束后又回到起始状态
和repeatCount比起来,repeatCount能够将动画重复两次(以下所示)或者任意次(你甚至可使用像1.5这样的分数来完成一个半动画)。一旦动画达到它的最终值,他就会立马跳回到初始值并从新开始

Figure 5.Repeat count可让动画运行超过一次
和repeat count相似,但不多用到的就是repeat duration了。它将会根据给定的一个duration简单的重复动画(以下2秒所示)。通过一个repeat duration时,若是它小于动画的duration,那么动画就会提早结束(repeat duration以后结束)

Figure 6.Repeat duration会让动画根据一个给定duration重复
这些均可以组合起来将一个反转动画重复屡次或在一个给定的duration间重复。

Figure 7.组合
一个跟时间相关有趣的属性是speed。经过设置duration为3秒,可是speed为2,动画快速的执行了1.5秒,由于它的速度是以前的两倍。

Figure 8.速度为2时,动画执行速度是以前的两倍,因此3秒的动画只须要执行1.5秒
若是只是配置了一个简单的动画,那么你也能够分开使用beginTime和duration以达到相同的效果。可是使用speed属性的优势在于这两个事实:
1.动画的speed是分等级的。(hierarchical)
2.CAAnimation不是惟一一个实现CAMediaTiming的类。
Hierarchical speed
速度为2的动画组有一部分动画的速度为1.5,那么这个动画就是3倍于正常速度。
CAMediaTiming的其余实现
CAMediaTiming是CAAnimation实现的一个协议,可是CALayer(全部Core Animationlayers的基类)也实现了相同的协议,这就意味着你能够设置layer的speed为2.0,这样,全部加入到layer的动画运行都要快两倍。一样的,若是一个速度为3的动画加到一个速度为0.5的layer上,这个动画最终将会以1.5倍的常速运行。
为了控制动画或layer的速度,一般还能够设置speed为0,从而暂停动画。和timeOffset结合在一块儿时,能够经过像slider相似的控件控制动画,咱们在下文中也会讲到。
刚开始timeOffset属性是很是奇怪的。正如名字所示那样,它对时间进行偏移(offset),从而计算出动画的状态。以下图所示。duration为3秒,offset为1秒的动画。

Figure 9.你能够offset整个动画,可是动画全部部分任然会执行
动画开始运行时跳过第一秒进入从橙色到蓝色的过分,直接运行剩下的两秒,直到彻底变蓝。而后动画直接跳回彻底橙色的时候,并完成第一秒的颜色转换。这看起来有点像咱们把动画的第一秒切下来,而后放到最后。
这个属性不多用,可是它能够和一个暂停的动画(speed=0)结合在一块儿控制’current time’。一个暂停的动画停留在第一帧。若是你观察offset动画每次的第一个颜色,你能够看到它的颜色值一秒就进入颜色转换。将time offset设置为其余值,你可让那段时间进入转换。
控制动画时间
同时使用speed和timeOffset能够控制动画的当前时间。这几乎不会涉及到什么代码,可是概念却比较难以理解(我但愿插图能有所帮助)。为了方便,我将动画的duration设为1.0。由于time offset是绝对值。这样作就意味着当time offset为0.0时,此时就是动画的0%处(动画开始),time offset为1.0时,就是动画的100%处(动画结束)。
Slider
以一个简单的例子开始,咱们为一个layer的背景色建立一个基本的动画并增长到layer上。将layer的速度设为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
当拖动slider时,在action方法中将slider的值(将slider的值设为0-1)设为layer的time offset
- - (IBAction)sliderChanged:(UISlider *)sender {
- self.myLayer.timeOffset = sender.value; // Update "current time"
- }
下面给出了效果图:当拖动slider时动画的值便会改变,而且更新layer的背景色

Figure 10.layer的颜色随着slider的值改变而改变
拉动刷新
你还可使用另外一种机制来控制动画时间:像scroll事件。这样能够建立一个自定义下拉刷新动画,当达到加载新数据的临界值以前,用户的拖拉操做都会产生一个动画。在个人这个例子中,scroll事件控制着一个路径画笔的动画。当达到临界值时,将会启动另外一种动画暗示新数据正在加载中。
此次咱们使用scroll view向下拖动的总量来控制时间,为了标准化,这个值将会以points的形式,这样是很是好的,由于咱们须要设置一个拖动的临界值来判断什么时候开始加载更多的数据。像下面那样处理代码。
- - (void)scrollViewDidScroll:(UIScrollView *)scrollView
- {
- CGFloat offset =
- scrollView.contentOffset.y+scrollView.contentInset.top;
- if (offset <= 0.0 && !self.isLoading) {
- CGFloat startLoadingThreshold = 60.0;
- CGFloat fractionDragged = -offset/startLoadingThreshold;
- self.pullToRefreshShape.timeOffset = MAX(0.0, fractionDragged);
- if (fractionDragged >= 1.0) {
- [self startLoading];
- }
- }
- }
像这样控制动画:
- CABasicAnimation *writeText =
- [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
- writeText.fromValue = @0;
- writeText.toValue = @1;
- CABasicAnimation *move =
- [CABasicAnimation animationWithKeyPath:@"position.y"];
- move.byValue = @(-22);
- move.toValue = @0;
- CAAnimationGroup *group = [CAAnimationGroup animation];
- group.duration = 1.0;
- group.animations = @[writeText, move];
结果是:下拉视图时,能够直接控制动画的进度。若是你抬起手动画将会退回去。

Figure 11.使用scroll事件直接控制拖动刷新
一旦超过临界值便真正开始加载动画以及加载更多的数据。我在scrollViewDidScroll:中经过设置isLoading防止timeOffset,开始加载动画并调整content inset以防止scrollview向上滚动时传递loading的指示。
- self.isLoading = YES;
- // start the loading animation
- [self.loadingShape addAnimation:[self loadingAnimation]
- forKey:@"Write that word"];
- CGFloat contentInset = self.collectionView.contentInset.top;
- CGFloat indicatorHeight = CGRectGetHeight(self.loadingIndicator.frame);
- // inset the top to keep the loading indicator on screen
- self.collectionView.contentInset =
- UIEdgeInsetsMake(contentInset+indicatorHeight, 0, 0, 0);
- self.collectionView.scrollEnabled = NO; // no further scrolling
- [self loadMoreDataWithAnimation:^{
- // during the reload animation (where new cells are inserted)
- self.collectionView.contentInset =
- UIEdgeInsetsMake(contentInset, 0, 0, 0);
- self.loadingIndicator.alpha = 0.0;
- } completion:^{
- // reset everything
- [self.loadingShape removeAllAnimations];
- self.loadingIndicator.alpha = 1.0;
- self.collectionView.scrollEnabled = YES;
- self.pullToRefreshShape.timeOffset = 0.0; // back to the start
- self.isLoading = NO;
- }];
最终,当你向下拖动超过临界值时便会像这样:

Figure 12.拖动刷新和加载动画
为你的应用增长像这样的动画能够很好的丰富应用。而且你能够像这样无需大量代码而作出高级的动画。这里我并无展现,可是你能够经过相似的,或手势识别或任何其余直接控制的机制。
示例代码可在GitHub上下载:
https://github.com/d-ronnqvist/blogpost-codesample-PullToRefresh