CAMediaTiming--beginTime、timeOffset
背景介绍:beginTime、timeOffset属性来自CAMediaTiming,一个处理时间的协议,被CALayer和CAAnimation实现。
它们的官方注释:

通过注释我们知道:
1.beginTime是相对于父对象的时间,当最后的父对象时会被设置为CACurrentMediaTime()值。(猜测)
2. t = (tp - begin) * speed + offset, 解释一下:(重点)
t:本对象时间(可能是CALayer和CAAnimation)
tp:父对象时间(可能是superLayer和CAAnimationGroup)
begin:即beginTime,开始时间
speed:时间流速
offset:一个初始偏移
begin、speed、offset是参数,tp才是变量,它会随着时间的流速,而递增
设置点通用前提,转换这个公式就可以得到:
t = CACurrentMediaTime() - begin + offset (当tp=CACurrentMediaTime(), speed=1)
这个转换代表着,对象在此刻的本地时间。
然而,光看注释无法解答我的问题:
1.它们怎么用?
2.CALayer和CAAnimation怎么混合使用这些属性?
3.本地时间和父对象时间的关系是什么?
4.怎样让动画组合在时间上衔接恰当?
注释无法解决,就百度、谷歌,如果还是找不到答案呢?
所以我通过测试的方法来深刻学习,也因此本文大多时间只是呈现出各种测试现象,需要读者自己通过这些现象去思考。demo地址:
https://github.com/jiliuliu/iOS-OC-Project
在进入正文之前,还需讲下两个知识点:
1.CACurrentMediaTime()
/* Returns the current CoreAnimation absolute time. This is the result of * calling mach_absolute_time () and converting the units to seconds. */ CA_EXTERN CFTimeInterval CACurrentMediaTime (void)
调用它可以获取一个时间,图层的时间线都是以它为基准。CACurrentMediaTime()将mach_absolute_time()返回的结果转化为秒得到。mach_absolute_time()返回cpu里内建时钟运转的周期数(ticks),这个tick数,在每次手机重启之后,会重新开始计数,而且iPhone锁屏进入休眠之后tick也会暂停计数。
2.
- (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(nullable CALayer *)l;
- (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(nullable CALayer *)l;
本图层时间和父图层时间的转换。
下面是正文:
CALayer和CAAnimation都有自己的时间线(time line),记录着每时每刻它们的状态(如颜色、形状、位置等),那什么是时间线呢?先理解下下面代码。
看如下代码:
- (void)test2 { CALayer *layer1 = [CALayer new]; CALayer *layer2 = [CALayer new]; [layer1 addSublayer:layer2]; [self.view.layer addSublayer:layer1]; layer2.beginTime = 3; layer2.speed = 2; layer2.timeOffset = 5; //获取图层在此刻的本地时间 CFTimeInterval absoluteTime = CACurrentMediaTime(); CFTimeInterval t = [layer1 convertTime:absoluteTime fromLayer:nil]; CFTimeInterval tp = [layer2 convertTime:absoluteTime toLayer:nil]; //验证公式t = (tp - begin) * speed + offset CFTimeInterval t0 = (tp - layer2.beginTime) * layer2.speed + layer2.timeOffset; NSLog(@"t: %.2lf", t); NSLog(@"(tp - begin) * speed + offset: %.2lf", t0); }
打印:
t: 375488.09 (tp - begin) * speed + offset: 375488.07
结果差不多相等,学会计算图层当前时刻的本地时间至关重要。
- (void)test1 { CALayer *layer1 = [CALayer new]; CALayer *layer2 = [CALayer new]; CALayer *layer3 = [CALayer new]; [layer1 addSublayer:layer2]; [layer2 addSublayer:layer3]; [self.view.layer addSublayer:layer1]; layer2.beginTime = 1; layer3.beginTime = 2; CFTimeInterval absoluteTime = CACurrentMediaTime(); CFTimeInterval localTime1 = [layer1 convertTime:absoluteTime fromLayer:nil]; CFTimeInterval localTime2 = [layer2 convertTime:absoluteTime fromLayer:nil]; CFTimeInterval localTime3 = [layer3 convertTime:absoluteTime fromLayer:nil]; NSLog(@"\nabsoluteTime:%2.lf, \nlocalTime1:%2.lf, \nlocalTime2:%2.lf, \nlocalTime3:%2.lf", absoluteTime, localTime1, localTime2, localTime3); CFTimeInterval localTime3f1 = [layer3 convertTime:absoluteTime fromLayer:layer1]; CFTimeInterval localTime3f2 = [layer3 convertTime:absoluteTime fromLayer:layer2]; CFTimeInterval localTime3t1 = [layer3 convertTime:absoluteTime toLayer:layer1]; CFTimeInterval localTime3t2 = [layer3 convertTime:absoluteTime toLayer:layer2]; CFTimeInterval localTime1f3 = [layer1 convertTime:absoluteTime fromLayer:layer3]; CFTimeInterval localTime1t3 = [layer1 convertTime:absoluteTime toLayer:layer3]; NSLog(@"图层间时间转换\nlocalTime3f1:%2.lf, \nlocalTime3f2:%2.lf, \nlocalTime3t1:%2.lf, \nlocalTime3t2:%2.lf, \nlocalTime1f3:%2.lf, \nlocalTime1t3:%2.lf", localTime3f1, localTime3f2, localTime3t1, localTime3t2, localTime1f3, localTime1t3); layer2.beginTime = 0; layer3.beginTime = 0; layer2.timeOffset = 1; layer3.timeOffset = 2; CFTimeInterval localTimeOffset1 = [layer1 convertTime:absoluteTime fromLayer:nil]; CFTimeInterval localTimeOffset2 = [layer2 convertTime:absoluteTime fromLayer:nil]; CFTimeInterval localTimeOffset3 = [layer3 convertTime:absoluteTime fromLayer:nil]; NSLog(@"\nlocalTimeOffset1:%2.lf, \nlocalTimeOffset2:%2.lf, \nlocalTimeOffset3:%2.lf", localTimeOffset1, localTimeOffset2, localTimeOffset3); }
打印:
absoluteTime:381518, localTime1:381518, localTime2:381517, localTime3:381515 图层间时间转换 localTime3f1:381515, localTime3f2:381516, localTime3t1:381521, localTime3t2:381520, localTime1f3:381521, localTime1t3:381515 localTimeOffset1:381518, localTimeOffset2:381519, localTimeOffset3:381521
通过打印信息,我们分析出:
a.子图层的时间线是建立在父图层的基础上。
b.若图层和其父图层的beginTime=0,timeOffset=1,speed=1,那么代表图层的本地时间和CACurrentMediaTime()当前时间同步。
c.通过设置beginTime、timeOffset、speed,图层可以控制自己时间线的起点、时间偏移、时间流速。让同一时刻,各个图层可以在属于自己的不同时空(即有自己的本地时间)。
d.beginTime和本地时间成反比,timeOffset成正比。
理论学完,我们看下实例,绘制图层的时间线。这里我要假设你对UIBezierPath动画已经很好掌握,不需要我说明这个动画的实现过程(毕竟UIBezierPath动画不是本文内容内)。
动画是这个样子滴:(最好自己下载demo来看效果,不然看我的文字描述,你不一定清楚)
图层上添加动画:指设置图层在某一时段上的状态变化。

代码1:
CFTimeInterval addAnimationTime = CACurrentMediaTime(); CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; animation.path = self.shapeLayer.path; animation.duration = 3; animation.delegate = self; animation.rotationMode = kCAAnimationRotateAuto; [self.airplaneLayer addAnimation:animation forKey:@"airplaneLayer"]; NSLog(@"addAnimation");
效果:飞机立即沿着绿色的线3秒内匀速飞行,然后瞬间回到原点。
解释效果:
定义了飞机图层在(addAnimationTime,addAnimationTime+3)时间范围内从曲线起点到终点的匀速运动。
代码2
CFTimeInterval addAnimationTime = CACurrentMediaTime(); CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; animation.beginTime = CACurrentMediaTime() + 2; //修改的代码 animation.path = self.shapeLayer.path; animation.duration = 3; animation.delegate = self; animation.rotationMode = kCAAnimationRotateAuto; [self.airplaneLayer addAnimation:animation forKey:@"airplaneLayer"];
效果:等待两秒后,飞机开始飞行。
解释效果:
定义了飞机图层在(addAnimationTime+2,addAnimationTime+5)时间范围内从曲线起点到终点的匀速运动。
而添加动画时图层的时间是addAnimationTime,所以要在动画添加2秒后才开始运动。
代码3
CFTimeInterval addAnimationTime = CACurrentMediaTime(); CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; animation.beginTime = CACurrentMediaTime() - 2; //修改的代码 animation.path = self.shapeLayer.path; animation.duration = 3; animation.delegate = self; animation.rotationMode = kCAAnimationRotateAuto; [self.airplaneLayer addAnimation:animation forKey:@"airplaneLayer"];
效果:飞机在轨道的三分之二的位置开始飞行,飞行到终点后,瞬间回到原点。
解释效果:
定义了飞机图层在(addAnimationTime-2,addAnimationTime+1)时间范围内从曲线起点到终点的匀速运动。
而添加动画时图层的时间是addAnimationTime,所以我们只能看见(addAnimationTime,addAnimationTime+1)时段的动画。
代码3
CFTimeInterval addAnimationTime = CACurrentMediaTime(); CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; animation.timeOffset = 2; //修改的代码 animation.path = self.shapeLayer.path; animation.duration = 3; animation.delegate = self; animation.rotationMode = kCAAnimationRotateAuto; [self.airplaneLayer addAnimation:animation forKey:@"airplaneLayer"];
效果:飞机在轨道的三分之二的位置开始飞行,飞行到终点后,又瞬间回到起点继续飞行,到轨道的三分之二的位置时,瞬间回到原点。
解释效果:
无论从未来,还是回到过去貌似都解释不通这个现象。动画的timeOffset属性真的很难解释,但使用却非常简单,这里就只能说会用就行了。
下面添加飞机动画的重启和暂停功能,以上面的飞机动画为例。
代码:
- (void)startAndStopAnimation { if (_airplaneLayer.speed) { CFTimeInterval pausedTime = [_airplaneLayer convertTime:CACurrentMediaTime() fromLayer:nil]; //动画暂停 _airplaneLayer.speed = 0.0; //如果_airplaneLayer.timeOffset=0,飞机会瞬间回到原点,这样设置后,飞机保留在暂停位置 _airplaneLayer.timeOffset = pausedTime; NSLog(@"pausedTime:%.2lf", _airplaneLayer.timeOffset); } else { CFTimeInterval pausedTime = _airplaneLayer.timeOffset; _airplaneLayer.beginTime = 0.0; //动画重启 _airplaneLayer.speed = 1.0; _airplaneLayer.timeOffset = 0.0; CFTimeInterval localTime = [_airplaneLayer convertTime:CACurrentMediaTime() fromLayer:nil]; //localTime - pausedTime代表的是暂停到重启的时间间隔 _airplaneLayer.beginTime = localTime - pausedTime; NSLog(@"timeSincePause:%.2lf", _airplaneLayer.beginTime); } }
startAndStopAnimation方法可能不好理解,可以做如下修改,自己测试效果:
- (void)startAndStopAnimation { _airplaneLayer.speed = !_airplaneLayer.speed; }
好了,就这样了。最后一点总结:
一切的秘密貌似都在t = (tp - begin) * speed + offset这个公式中。
不行,我还没死,我还要说一句:
为什么两个属性,我却写了这么多,花了这么多时间,难道我真的很笨?高手都是看眼注释就什么都明白了?

浙公网安备 33010602011771号