转载自:https://juejin.cn/post/6844903881084780557
最近学习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,即将动画向前位移到上次停下来的时间点。
作者:if_else工程师
链接:https://juejin.cn/post/6844903881084780557
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。