背景介绍:beginTime、timeOffset属性来自CAMediaTiming,一个处理时间的协议,被CALayer和CAAnimation实现。
它们的官方注释:
通过注释我们知道:
1.beginTime是相对于父对象的时间(也就是说是个相对值?)
2. t = (tp - begin) * speed + offset, 解释一下:
t:本对象时间(可能是CALayer和CAAnimation)
tp:父对象时间(可能是superLayer和CAAnimationGroup)
begin:即beginTime,开始时间
speed:速度
设置点通用前提,转换这个公式就可以得到:
3.timeOffset = t - (tp - begin) (当speed=1)
4.begin = tp - t (当speed=1, timeOffset = 0)
5.timeOffset = t - tp (当speed=1, begin = 0)
注意4 和 5, 它们等号右边为相反数,这就很有意思了,记住它,后面的boss能不能攻克就靠这点有关
然而,光看注释无法解答我的问题:
1.它们怎么用?
2.CALayer和CAAnimation怎么混合使用这些属性?
3.本地时间和父对象时间的关系是什么?
4.怎样让动画组合在时间上衔接恰当?
注释无法解决,就百度、谷歌,如果还是找不到答案呢?
所以我通过测试的方法来深刻学习,也因此本文大多时间只是呈现出各种测试现象,需要读者自己通过这些现象自己思考。demo地址:(稍后上传)
在进入正文之前,还需讲下两个知识点:
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;
本图层时间和父图层时间的转换。
先举个列子,让我们对beginTime、timeOffset有个简单的了解,这里我要假设你对UIBezierPath动画已经很好掌握(毕竟UIBezierPath动画不是本文内容内)。
动画是这个样子滴:(最后自己下载demo来看效果,不然看我的文字描述,你不一定清楚)
点击add按钮就执行下面代码,飞机立即沿着绿色的线3秒内匀速飞行,然后回到原点。
代码1:
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"];
a.然后我添加animation.beginTime = 2,运行后飞机没动。
b.然后修改为animation.beginTime = CACurrentMediaTime() + 2,运行后飞机等待2秒后开始动画。
c.继续修改animation.beginTime = CACurrentMediaTime() - 2,运行后飞机立即开始动画,但只执行了2~3秒(即动画循环的最后一秒)的动画。
通过这三个测试,我们发现有点意思了。
我们推测出当animation.beginTime等于0时,会被系统替换成CACurrentMediaTime(),代表当前时间,立即开始动画,所以加一减一分别代表过去和将来。
脑洞再大一点,我们不该把自己限制在0~3秒的时间范围里,而应该是(-∞,+∞)时间线上。
图1 x:时间
图2 x:时间
所谓的beginTime就是在图2的x轴上移动。对,就是这样,一目了然。
下面我们把beginTime的设置删掉,我们测算下timeOffset。
a.在代码1中添加animation.timeOffset= 2,效果为飞机在轨道的三分之二的位置开始飞行,飞行到终点后,又瞬间回到起点继续运行到轨道的三分之二的位置,动画终止。
b.修改成animation.timeOffset = -1,效果同上。
对比下beginTime,发现它们实现的效果是有所类似的,但又有区别。
类似的地方是:它们都可以改变飞机飞行起点。
不同处:beginTime设置为将来时间时会等待执行,但timeOffset都是立即执行;
beginTime设置为过去时间时动画时间不足duration时间,但timeOffset会执行完duration时间。
不同处,我们可以与前面推导的公式4和公式5联系起来想想。
接下来,测试t = (tp - begin) * speed + offset及分析本地时间和父对象时间的关系
代码2
- (void)test { 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); }
打印:
absoluteTime:374474, localTime1:374474, localTime2:374473, localTime3:374471 图层间时间转换 localTime3f1:374471, localTime3f2:374472, localTime3t1:374477, localTime3t2:374476, localTime1f3:374477, localTime1t3:374471
这里我就不过多分析了,读者自己考虑下,提一下就是localTime3f1和localTime1t3结果相同。
代码3
/* Additional offset in active local time. i.e. to convert from parent * time tp to active local time t: t = (tp - begin) * speed + offset. * 验证这句话 */ - (void)test2 { CALayer *layer1 = [CALayer new]; CALayer *layer2 = [CALayer new]; CALayer *layer3 = [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]; 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
或许你认为对beginTime、timeOffset的讲解快完了,它们也就这样,但事实是这只是开始。
通过飞机动画,我们了解到它们在CAAnimation上的作用,但它们在CALayer上的效果又是怎样的呢?对CAAnimation又有什么影响呢?
好了,现在我们对beginTime、timeOffset有了一定了解,下面让我们暂定、重启一个动画来实践下。