阅读 237

iOS动画暂停与恢复的理解

最近学习iOS动画相关的知识,学习到控制动画的暂定与恢复的时候,对其中的timeOffset,beginTime,fillMode等概念不太理解,遂查阅资料,学习一个。

官方文档中给出的暂停与恢复layer动画的代码如下:

-(void)pauseLayer:(CALayer*)layer {
   CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
   layer.speed = 0.0;
   layer.timeOffset = pausedTime;
}
 
-(void)resumeLayer:(CALayer*)layer {
   CFTimeInterval pausedTime = [layer timeOffset];
   layer.speed = 1.0;
   layer.timeOffset = 0.0;
   layer.beginTime = 0.0;
   CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
   layer.beginTime = timeSincePause;
}复制代码

懵逼点: 

1. CACurrentMediaTime()是啥?

2. layer对象调用的convertTime: fromLayer:是啥意思?为什么要这样做? 

3. timeOffset是啥?暂停动画为什么要设置这个?  

4. beginTime是啥?为什么要设置两次beginTime?第一次设置为0,第二次设置为timeSincePause? 

 5. timeSincePause是怎么算的?


iOS的时间

mach_absolute_time()

mach_absolute_time()可能用到的同学比较少,但这个概念非常重要。  

描述绝对时间需要找到一个均匀变化的属性值来描述时间,CPU的时钟周期数(ticks)刚好就是这样的一个属性。这个tick的数值可以用来描述时间,而mach_absolute_time()返回的就是CPU已经运行的tick的数量。将这个tick数经过一定的转换就可以变成秒数,或者纳秒数,这样就和时间直接关联了。 

不过这个tick数,在每次手机重启之后,会重新开始计数,而且iPhone锁屏进入休眠之后tick也会暂停计数。 

 mach_absolute_time()不会受系统时间影响,只受设备重启和休眠行为影响。

CACurrentMediaTime()

CACurrentMediaTime()可能接触到的同学会多一些,先看下官方的定义:

/* Returns the current CoreAnimation absolute time. This is the result of
 * calling mach_absolute_time () and converting the units to seconds. */
CFTimeInterval CACurrentMediaTime (void)复制代码

CACurrentMediaTime()就是将上面mach_absolute_time()的CPU tick数转化成秒数的结果。以下代码: 

double mediaTime = CACurrentMediaTime();
NSLog(@"CACurrentMediaTime: %f", mediaTime);复制代码

返回的就是开机后设备一共运行了(设备休眠不统计在内)多少秒,另一个API也能返回相同的值:

NSTimeInterval systemUptime = [[NSProcessInfo processInfo] systemUptime];
NSLog(@"systemUptime: %f", systemUptime);复制代码

CACurrentMediaTime()也不会受系统时间影响,只受设备重启和休眠行为影响。


iOS动画的时序

iOS动画的时序的计算是理解以上代码最关键的一点。在CAMediaTiming协议的timeOffset的注释上,官方给出一下公式:

t = (tp - begin) * speed + offset

t的是就是需要计算的动画的时间点

tp是父layer的时间点,为了方便理解,可以认为是绝对时间,随时间流逝而增加。

begin、speed、offset就是动画的属性beginTime、speed、timeOffset。

下面根据以上公式,来尝试暂停和恢复动画


动画的暂停

默认情况下,speed等于1,begin等于0,offset等于0。带入等式,得到

t = tp

如果想要暂停动画,毫无疑问,需要将speed设置为0。

但是如果speed等于0,上面的等式就不成立了。将speed等于0带入上面的等式,得到

t = offset

而offset默认等于0,动画就回到了最初的位置了!因此,需要将暂停时的 t 的值,赋值给offset,这样,t 的值就可以维持在暂停的时候了。所以设置 offset = pausedTime

-(void)pauseLayer:(CALayer*)layer {
   CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
   layer.speed = 0.0;
   layer.timeOffset = pausedTime;
}复制代码

动画的恢复

要想恢复动画,需要做两点:

1. 将速度调整为1,同时将offset恢复成0,不然下次暂停时,offset就不对了。

2. t 的值要等于上面暂停时的值。因为动画要从暂停的时候继续往下播放

t =(tp-begin)*speed + offset

暂停的时间点还是上面算出来的pausedTime,所以需要构造等式,使得在speed等于1,offset等于0的情况下,使得等式左边的 t 等于暂停时算出来的timeOffset。即

t = tp-begin = pausedTime

所以 begin 要等于 tp - pausedTime,代码如下

-(void)resumeLayer:(CALayer*)layer {
   CFTimeInterval pausedTime = [layer timeOffset];
   layer.speed = 1.0;
   layer.timeOffset = 0.0;
   layer.beginTime = 0.0;
   CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
   layer.beginTime = timeSincePause;
}复制代码

那么,为什么要先将beginTime设置为0,再将 beginTime 设置为 tp - pausedTime 呢?

因为 convertTime:fromLayer: 在计算当前layer的时间时,会使用到 layer 的beginTime,

    self.nicoLayer.beginTime = 0;
    NSLog(@"beginTime1 = %f", self.nicoLayer.beginTime);
    CFTimeInterval tp1 = [self.nicoLayer convertTime:CACurrentMediaTime() fromLayer:nil];
    NSLog(@"tp1 = %f", tp1);
    
    self.nicoLayer.beginTime = 10;
    NSLog(@"beginTime2 = %f", self.nicoLayer.beginTime);
    CFTimeInterval tp2 = [self.nicoLayer convertTime:CACurrentMediaTime() fromLayer:nil];
    NSLog(@"tp2 = %f", tp2);复制代码

打印结果:

    beginTime1 = 0.000000
    tp1 = 81431.807847
    beginTime2 = 10.000000
    tp2 = 81421.808063复制代码

由此可见,convertTime:fromLayer: 方法在计算的时候,会使用到上面的公式,所以需要将beginTime先恢复成0,再进行计算,才能得到正确的时间。


简单理解:

1. beginTime为正的时候,计算动画时间的时候,时间点会往前位移beginTime秒

2. timeOffset为正的时候,计算动画时间的时候,时间点会往后位移timeOffset秒


懵逼点理解:

1. CACurrentMediaTime()是啥?

答:CACurrentMediaTime()就是将上面mach_absolute_time()的CPU tick数转化成秒数的结果。CACurrentMediaTime()也不会受系统时间影响,只受设备重启和休眠行为影响。

2. layer对象调用的convertTime: fromLayer:是啥意思?为什么要这样做?

答:计算当前layer的绝对时间

3. timeOffset是啥?暂停动画为什么要设置这个?

答:动画时间点的计算满足一个公式,t = (tp - begin) * speed + offset,设置timeOffset使得公式在speed等于0的情况下,动画的时间点(或位置)等于暂停时的时间(或位置)

4. beginTime是啥?为什么要设置两次beginTime?第一次设置为0,第二次设置为timeSincePause?

答:同上,是为了在speed设置为1的情况下,使等式成立。

5. timeSincePause是怎么算的?

答:tp是当前时间点,pausedTime是暂停时的时间点,pausedTime是固定不变的,tp随时间流逝而增加,所以timeSincePause是从暂停到当前时间的间隔时长,beginTime设置为timeSincePause,即将动画向前位移到上次停下来的时间点。


参考

Apple Document: Pausing and Resuming Animations

Stack Overflow: Comprehend pause and resume animation on a layer

MrPeak: iOS关于时间的处理


关注下面的标签,发现更多相似文章
评论