在CAAnimation中暂停动画

使用CAAnimation做动画时,如果要移除动画可以使用

- (void)removeAnimationForKey:(NSString *)key;
- (void)removeAllAnimations;

移除动画后,根据动画 fillmode 的设置,视图会静止在动画起点或者终点的位置,并且动画再次启动还是从起点开始. 如果想让它暂停在当前位置,继续动画时从当前位置开始继续动画,可以根据暂停动画的时间计算暂停位置,然后继续动画时重新制作新的动画.

//计算当前视图位置
-(void)resetViewWithPosition:(float)position
{
CGRect rect = self.animateView.frame;
if (self.isVertical)
{
rect.origin.y = position;
}
else
{
rect.origin.x = position;
}
self.animateView.frame = rect;
}
//暂停动画
- (void)nx_pauseAnimate
{
if (!_isStart || _isPause)
{
return;
}
_isPause = YES;
[self.animateView.layer removeAllAnimations];
//动画已经进行的的时间
_animateTime = _animateTime + [[NSDate date] timeIntervalSince1970] - _startTime;
//当前的位置
float pausePosition = [_animation positionWithPauseTime:_animateTime];
float scap = (self.isVertical ? CGRectGetHeight(self.animateView.frame) :CGRectGetWidth(self.animateView.frame) )/2.;
[self resetViewWithPosition:(pausePosition -scap)];
}
//继续动画
- (void)nx_resumeAnimate
{
if (!_isStart || !_isPause)
{
return;
}
_isPause = NO;
//继续动画
_startTime = [[NSDate date] timeIntervalSince1970];
NSArray * keyPathArray = [_animation keyPathArrayWithPauseTime:_animateTime];
CAKeyframeAnimation * animate = [CAKeyframeAnimation animationWithKeyPath:_animation.keyPath];
animate.values = keyPathArray;
animate.duration = _animation.duration - _animateTime;
[self.animateView.layer addAnimation:animate forKey:_animation.keyPath];
[self resetViewWithPosition:_endPosition];
}

上面的方法也可以实现,但是每次都要重新创建动画,实在不是高明的方法.其实利用CAAnimation自身的属性,有更方便的方法.首先了解一下需要使用的这些属性

  • beginTime指定了动画开始之前的的延迟时间.这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0(就是说动画会立刻执行).

  • speed是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度.如果2.0的速度,那么对于一个duration为1的动画,实际上在0.5秒的时候就已经完成了.当这个值为0时,动画静止.

  • timeOffset和beginTime类似,但是和增加beginTime导致的延迟动画不同,增加timeOffset只是让动画快进到某一点,例如,对于一个持续1秒的动画来说,设置timeOffset为0.5意味着动画将从一半的地方开始.

  • beginTime会受speed的影响,timeOffset并不受speed的影响.所以如果你把speed设为2.0,把timeOffset设置为0.5,那么你的动画将从动画最后结束的地方开始,因为1秒的动画实际上被缩短到了0.5秒.然而即使使用了timeOffset让动画从结束的地方开始,它仍然播放了一个完整的时长,这个动画仅仅是循环了一圈,然后从头开始播放.

全局时间

CoreAnimation有一个全局时间的概念,称作马赫时间(“马赫”实际上是 iOS和Mac OS系统内核的命名),它可以通过CACurrentMediaTime()获取.它与NSDate 或 CFAbsoluteTimeGetCurrent()不同, 后两者返回的时钟时间将会会网络时间同步,而CACurrentMediaTime() 是基于内建时钟的,不会因为外部时间变化而变化,这个方法获取到的时间,是手机从开机一直到当前所经过的秒数.

为了暂停在正确的位置以及继续动画时起点的位置正确,需要使用这个马赫时间来计算.

每个CALayer和CAAnimation实例都有自己本地时间的概念,是根据父图层/动画层级关系中的beginTime,timeOffset和speed属性计算。就和转换不同图层之间坐标关系一样,CALayer同样也提供了方法来转换不同图层之间的本地时间。

- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l;

暂停/继续动画

利用CAAnimation的属性和全局时间,就可以实现暂停/继续动画了.代码如下:

//暂停动画
- (void)nx_pauseAnimate
{
if (!_isStart || _isPause)
{
return;
}
_isPause = YES;
CFTimeInterval pauseTime = [self.animateView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
// 设置layer的timeOffset, 在继续操作也会使用到
self.animateView.layer.timeOffset = pauseTime;
// local time与parent time的比例为0, 意味着local time暂停了
self.animateView.layer.speed = 0;
}
//继续动画
- (void)nx_resumeAnimate
{
if (!_isStart || !_isPause)
{
return;
}
_isPause = NO;
// 时间转换
CFTimeInterval pauseTime = self.animateView.layer.timeOffset;
// 计算暂停时间
CFTimeInterval timeSincePause = CACurrentMediaTime() - pauseTime;
// 取消
self.animateView.layer.timeOffset = 0;
// local time相对于parent time世界的beginTime
self.animateView.layer.beginTime = timeSincePause;
// 继续
self.animateView.layer.speed = 1;
}