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这个公式中。

不行,我还没死,我还要说一句:

  为什么两个属性,我却写了这么多,花了这么多时间,难道我真的很笨?高手都是看眼注释就什么都明白了?

posted @ 2018-04-05 15:58  艺叶亦华  阅读(1226)  评论(0)    收藏  举报