【问题标题】:Comprehend pause and resume animation on a layer理解图层上的暂停和恢复动画
【发布时间】:2014-01-06 09:10:08
【问题描述】:

我正在学习 Core Animation Programming Guide 中的动画,但我无法理解图层上的暂停和恢复动画。

文档告诉我如何暂停和恢复动画,但没有明确解释。我觉得关键是要了解CAlayer的timeOffset和beginTime方法是什么。

这些代码是暂停和恢复动画。在resumeLayer 方法中,layer.beginTime = timeSincePause; 这行真的让我很困惑。

-(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;
}

任何帮助将不胜感激。

【问题讨论】:

  • 您看过 CAMediaTiming 协议参考吗? link
  • @Bamsworld 谢谢你的链接。我见过它,但它说的是模棱两可。
  • 好的,这个呢? linkAnimation Types and Timing Programming Guide(我觉得这个很全面,解释了时间和时间空间。)
  • This article 将真正帮助您了解动画时序
  • @Alladinian +1,它很有用,可以帮助我理解timeOffset,但我仍然对resumeLayer 方法感到困惑。

标签: ios iphone animation core-animation calayer


【解决方案1】:

我们来测试一个层的两个属性:beginTime 和 timeOffset。

先决条件

我们使用[layer convertTime:CACurrentMediaTime() fromLayer:nil]获取CALayer的时间空间

1、给layer的beginTime赋值5.0(beginTime之前是0):

NSLog(@"CACurrentMediaTime:%f", [t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
t1.layer.beginTime = 5.0 ;
NSLog(@"CACurrentMediaTime:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;

结果日志是:

2014-01-15 11:00:33.811 newUserInterface[1404:70b] CACurrentMediaTime:7206.884498
2014-01-15 11:00:33.811 newUserInterface[1404:70b] CACurrentMediaTime:7201.885088

结果显示,如果我在beginTime 上加上5.0,图层的时间将减去5.0。如果动画正在播放,在beginTime 上添加 5.0 将导致动画重做 5.0 秒前的动画。

2、给layer的timeOffset赋值5.0(timeOffset之前是0):

NSLog(@"CACurrentMediaTime:%f", [t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
t1.layer.timeOffset = 5.0 ;
NSLog(@"CACurrentMediaTime:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;

结果是:

2014-01-15 11:09:07.757 newUserInterface[1449:70b] CACurrentMediaTime:7720.851464
2014-01-15 11:09:07.758 newUserInterface[1449:70b] CACurrentMediaTime:7725.852011

结果显示如果我在timeOffset上加5.0,图层的时间会加5.0。如果动画在飞行中,在 timeOffset 上添加 5.0 将导致动画跳转到 5.0 秒后的动画。

理解图层上的暂停和恢复动画

这里是一个例子,t1 是 UIViewController 根视图的子视图。我在 t1 上做了一个动画,它为t1.layer 的位置设置了动画。

如果给图层添加了动画,图层会根据动画的beginTime计算动画的时间,如果beginTime为0,则立即进行动画。

CABasicAnimation * b1 = [CABasicAnimation animationWithKeyPath:@"position"] ;
b1.toValue = [NSValue valueWithCGPoint:CGPointMake(160.0, 320.0)] ;
b1.duration = 10.0f ;
[t1.layer addAnimation:b1 forKey:@"pos"] ;
NSLog(@"CACurrentMediaTime:%f", [t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;

日志显示2014-01-15 11:25:53.975 newUserInterface[1530:70b] CACurrentMediaTime:8727.108740,表示动画将在 t1.layer 的时间空间中从 8727 开始并在 8727+10 停止。

当动画在飞行时,我使用- (void)pauseLayer:(CALayer*)layer 方法暂停动画。

layer.speed = 0.0; 会导致图层停止,图层的时间会被设置为 0。(我知道因为当设置 layer.speed 为 0 时,我会立即获取图层的时间并记录它)

layer.timeOffset = pausedTime; 会在 layer 的时间上加上 pauseTime(假设 layer.timeOffset 为 0),现在 layer 的时间是 pausedTime。

- (void)pauseLayer:(CALayer *)layer
{
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; // pauseTime is the time with respect to layer's time space
    layer.speed = 0.0; // layer's local time is 0
    layer.timeOffset = pausedTime; // layer's local time is pausedTime, so animation stop here
}

然后我将使用- (void)resumeLayer:(CALayer*)layer 方法恢复动画。

-(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;
}

如果我在 8727+1 处停止动画(表示动画动画为 1 秒),在 pauseLayer 方法中,layer.speed = 0 会将图层的时间设置为 0,layer.timeOffset = pausedTime; 将在图层的时间上添加 pausedTime,所以图层的时间是 pausedTime。

等一下,我们现在来做个总结。 layer.speed 为 0.0,'layer.timeOffset' 等于 pausedTime,即 8727+1,图层的时间也是 pausedTime。请记住,我们很快就会使用它们。

让我们继续,我用resumeLayer方法在8727+11处恢复动画,layer.speed = 1.0;会在层的时间上加上8727+11,所以层的时间是8727+1+8727+11,layer.timeOffset = 0.0;导致layer 的时间减去 8727+1 因为 layer.timeOffset 之前是 8727+1,现在 layer 的本地时间是 8727+11。 timeSincePause 是 (8727+11-8727-1)=10。

layer.beginTime = timeSincePause; 导致图层时间减 10。现在图层的本地时间是 8727+1,这是我暂停动画的时间。

我会给你看代码和日志:

- (void)pauseLayer:(CALayer *)layer
{
    NSLog(@"%f", CACurrentMediaTime()) ;
    NSLog(@"pauseLayer begin:%f", [t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil] ;
    layer.speed = 0.0 ;
    NSLog(@"pauseLayer after set speed to 0:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
    layer.timeOffset = pausedTime ;
    NSLog(@"pauseLayer after set timeOffset:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
}

- (void)resumeLayer:(CALayer *)layer
{
    NSLog(@"%f", CACurrentMediaTime()) ;
    NSLog(@"resumeLayer begin:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
    CFTimeInterval pausedTime = layer.timeOffset ;
    layer.speed = 1.0 ;
    NSLog(@"resumeLayer after set speed to 1:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
    layer.timeOffset = 0.0;
    NSLog(@"resumeLayer after set timeOffset to 0:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
    layer.beginTime = 0.0 ;
    NSLog(@"resumeLayer after set beginTime to 0:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime ;
    layer.beginTime = timeSincePause ;
    NSLog(@"resumeLayer after set beginTime to timeSincePause:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
}

日志:

2014-01-15 13:14:34.157 newUserInterface[1762:70b] 15247.550325
2014-01-15 13:14:34.158 newUserInterface[1762:70b] pauseLayer begin:15247.550826
2014-01-15 13:14:34.158 newUserInterface[1762:70b] pauseLayer after set speed to 0:0.000000
2014-01-15 13:14:34.159 newUserInterface[1762:70b] pauseLayer after set timeOffset:15247.551284

2014-01-15 13:14:40.557 newUserInterface[1762:70b] 15253.950505
2014-01-15 13:14:40.558 newUserInterface[1762:70b] resumeLayer begin:15247.551284
2014-01-15 13:14:40.558 newUserInterface[1762:70b] resumeLayer after set speed to 1:30501.502810
2014-01-15 13:14:40.559 newUserInterface[1762:70b] resumeLayer after set timeOffset to 0:15253.952031
2014-01-15 13:14:40.559 newUserInterface[1762:70b] resumeLayer after set beginTime to 0:15253.952523
2014-01-15 13:14:40.560 newUserInterface[1762:70b] resumeLayer after set beginTime to timeSincePause:15247.551294

另一个问题在resumeLayer方法:为什么不将两个赋值行合并为一个layer.beginTime = timeSincePause;,原因在[layer convertTime:CACurrentMediaTime() fromLayer:nil],结果值与layer.beginTime有关。

   layer.beginTime = 0.0;
   CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
   layer.beginTime = timeSincePause;

说实话,我还是不知道动画背后是怎么做的,我在做的是分析结果,不是很好的解决方案。我很高兴任何对此有想法的人都可以分享。谢谢!

【讨论】:

  • @Unheilig 不,将 10 分配给 beginTime 将导致图层的语言环境时间减少 10。顺便说一句,我已经更新了我的答案,这更清楚了。
  • @KudoCC 感谢分享,我在同一个话题上苦苦挣扎,我尝试在每一步都采用与您相同的日志记录技术,并解决了我遇到的一些动画问题。
  • 这是一个绝妙的解释!我复制了日志并尝试了,它变得非常清晰。谢谢@KudoCC。解决了我的问题。
  • 如何停止动画(暂停+倒带),以便在下次恢复时从时间 0 开始?
【解决方案2】:

在CAMediaTiming的头文件中,我们可以看到这些代码:

/* The begin time of the object, in relation to its parent object, if
 * applicable. Defaults to 0. */

@property CFTimeInterval beginTime;

/* The basic duration of the object. Defaults to 0. */

@property CFTimeInterval duration;

/* The rate of the layer. Used to scale parent time to local time, e.g.
 * if rate is 2, local time progresses twice as fast as parent time.
 * Defaults to 1. */

@property float speed;

/* Additional offset in active local time. i.e. to convert from parent
 * time tp to active local time t: t = (tp - begin) * speed + offset.
 * One use of this is to "pause" a layer by setting `speed' to zero and
 * `offset' to a suitable value. Defaults to 0. */

@property CFTimeInterval timeOffset;

重要的是公式:

t = (tp - begin) * speed + offset

此公式定义全局时间(或父时间,tp)如何映射到图层的本地时间。这个公式可以解释所有列出的代码:

  1. 在时间 A,动画暂停。设置speed = 0,timeOffset = pauseTime后,图层的本地时间等于pauseTime。并且本地时间不会再增加,因为 speed = 0;
  2. 在时间 B,动画恢复。设置 speed = 1.0, timeOffset = 0, beginTime = 0 后,图层的本地时间等于全局时间(或 tp),即 (timePause + timeSinacePause)。但是我们需要动画从时间点#A开始,所以我们设置beginTime = timeSincePaused,然后图层的本地时间就等于timePause。当然,这会导致动画从暂停点继续。

【讨论】:

  • 太好了,这就是我在等待的!我认为当我们向图层添加动画时,图层会保持本地时间的开始和结束。然后随着它的超级时间(tp)增加,动画动画。 layer.beginTime = 0.0; 用于在pauseLayer 方法中调用CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; 之前恢复图层的本地时间相关状态。
  • 顺便说一句,你用什么工具来绘制图表?
  • @KudoCC 这只是 Microsoft Excel :)
【解决方案3】:

来自docs:

开始时间

beginTime 指定接收器相对于它的开始时间 父对象(如果适用)。 (必填)

这里有一个很好的解释,取自here:

如果动画在动画组中,beginTime 是从 其父对象的开始 - 动画组。因此,如果 动画的 beginTime 为 5,它在 5 秒后开始 动画组开始。

如果动画直接添加到图层,beginTime 仍然是 从其父对象的开头偏移 - 层。但由于 一个层的开始是过去的1,我不能简单地设置 beginTime 为 5 将动画延迟 5 秒,因为 5 秒 开始一层之后大概还是过去的时间。我什么 通常真正想要的是相对于添加动画时的延迟 到层——用 addTime 表示。

这是问题中添加了日志的代码:

- (IBAction)start:(UIButton *)sender
{    
    [UIView animateWithDuration:10 animations:^() {//Move square to x=300
    }completion:^(BOOL finished){}];
}

- (IBAction)pause:(UIButton *)sender
{
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.speed = 0.0;
    layer.timeOffset = pausedTime;
    NSLog(@"pausedTime: %f",pausedTime);
}

- (IBAction)resume:(UIButton *)sender
{
    CFTimeInterval pausedTime = [layer timeOffset];
    layer.speed = 1.0;
    layer.timeOffset = 0.0;
    layer.beginTime = 0.0;
    NSLog(@"CACurrentMediaTime: %f",[layer convertTime:CACurrentMediaTime() fromLayer:nil]);
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    NSLog(@"timeSincePause: %f",timeSincePause);
    layer.beginTime = timeSincePause;
}

输出:

pausedTime: 20000
CACurrentMediaTime: 20005
timeSincePause: 5 // <- that's your begin time. When you hit resume you want to begin the animation from that relative time.

总结一下,

动画持续时间总共是 10,我在 5 点停止了动画,我还希望它是我恢复动画时的 beginTime。

所以,我保存暂停时间并从当前时间中减去它,以获得已经过去的相对动画时间。

【讨论】:

  • 感谢您的回答。但我认为你错过了一些重要的事情。首先,您说的是添加到组动画或添加到图层中的动画的开始时间,但我想知道的是图层的开始时间。二、resume方法中,先layer.beginTime = 0.0;再layer.beginTime = timeSincePause;,为什么不直接把layer.beginTime赋值给timeSincePause。我把layer.beginTime = 0.0;注释掉了,但是动画很奇怪。
【解决方案4】:

我认为 resume 功能仅适用于 speed = 1,如果 speed 不等于 1,则 beginTime 必须除以要纠正的速度,因为 beginTime 是父时间类型,而不是本地时间的 line timeOffset。

【讨论】:

    猜你喜欢
    • 2018-04-21
    • 1970-01-01
    • 2012-04-17
    • 2015-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-31
    • 1970-01-01
    相关资源
    最近更新 更多