Comprehend pause and resume animation on a layer

孤街浪徒 提交于 2020-06-24 05:10:08

问题


I am studying Animation in Core Animation Programming Guide and I get stuck on comprehending pause and resume animation on a layer.

The document tells me how to pause and resume animation without clear explanation. I think the key is to understand what is timeOffset and beginTime method of CAlayer.

These code is pause and resume animation. In resumeLayer method, layer.beginTime = timeSincePause; this line really make me confused.

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

Any help will be appreciated.


回答1:


In the header file of CAMediaTiming, we can see these codes:

/* 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;

What important is the formula:

t = (tp - begin) * speed + offset

This formula defines how the global time (or parent time, tp) maps to the local time of the layer. And this formula can explain everything of the listed codes:

  1. At time A, the animation is paused. After sets the speed = 0, and timeOffset = pauseTime, the local time of the layer is equal pauseTime. And local time will not increase any more because speed = 0;
  2. At time B, the animation is resumed. After sets the speed = 1.0, timeOffset = 0, beginTime = 0, the layer's local time is equal to the global time (or tp) which is (timePause + timeSinacePause). But we need the animation starts from the time point #A, so we set the beginTime = timeSincePaused, and then the layer's local time is equal to timePause. Of cause, this incurs the animation continue from the point paused.




回答2:


Let's have a test at the two properties of a layer: beginTime and timeOffset.

Prerequisite

We get the CALayer's time space using [layer convertTime:CACurrentMediaTime() fromLayer:nil]

1、Assign 5.0 to layer's beginTime (beginTime is 0 before) :

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

result log is:

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

The result shows that if I add 5.0 on beginTime, the layer's time will minus 5.0. If an animation is in flight, adding 5.0 on beginTime will cause the animation redo the animation 5.0 seconds ago.

2、Assign 5.0 to layer's timeOffset (timeOffset is 0 before):

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

result is:

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

The result shows that if I add 5.0 on timeOffset, the layer's time will add 5.0. If an animation is in flight, adding 5.0 on timeOffset will cause the animation jump to the animation it would do 5.0 seconds later.

Comprehend pause and resume animation on a layer

Here is an example, t1 is an subview of UIViewController's root view. I do an animation on t1 which animates the position of t1.layer.

If an animation is added to a layer, the layer will calculate when to animate the animation according to the animation's beginTime, if the beginTime is 0, it will animate it immediately.

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]) ;

log shows 2014-01-15 11:25:53.975 newUserInterface[1530:70b] CACurrentMediaTime:8727.108740, which means the animation will begin at 8727 and stop at 8727+10 in t1.layer's time space.

When the animation is in flight, and I pause the animation using - (void)pauseLayer:(CALayer*)layer method.

layer.speed = 0.0; will cause the layer stop and layer's time will be set to 0. (I know that because when set layer.speed to 0, I immediately fetch the layer's time and log it)

layer.timeOffset = pausedTime; will add pauseTime to layer's time(assuming layer.timeOffset is 0), now the layer's time is 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
}

Then I will resume the animation using - (void)resumeLayer:(CALayer*)layer method.

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

If I stop the animation at 8727+1(means the animation animate for 1 second), in pauseLayer method, layer.speed = 0 will set layer's time to 0 and layer.timeOffset = pausedTime; will add pausedTime on layer's time, so the layer's time is pausedTime.

Wait for a moment, let's have a summary now. layer.speed is 0.0, 'layer.timeOffset' is equal to pausedTime which is 8727+1 and layer's time is pausedTime too. Please keep in your mind, we will use them soon.

Let's continue, I resume the animation at 8727+11 with resumeLayer method, layer.speed = 1.0; it will add 8727+11 on layer's time, so layer's time is 8727+1+8727+11, layer.timeOffset = 0.0; it causes layer's time minus 8727+1 because layer.timeOffset is 8727+1 before, layer's local time is 8727+11 now. timeSincePause is (8727+11-8727-1)=10.

layer.beginTime = timeSincePause; it cause layer's time minus 10. Now layer's local time is 8727+1 which is the time I paused animation.

I will show you the code and log:

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

log:

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

Anther question is in resumeLayer method: why not combine the two assignment line to one layer.beginTime = timeSincePause;, the reason is in [layer convertTime:CACurrentMediaTime() fromLayer:nil], the result value is related to layer.beginTime.

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

Tell the truth, I still don't know how animation works behind, what I am doing is analyzing the result, it's not a good solution. I'm very glad that anyone who has ideas on this could share. Thanks!




回答3:


From the docs:

beginTime

beginTime Specifies the begin time of the receiver in relation to its parent object, if applicable. (required)

Here's a great explanation of it taken from here:

If an animation is in an animation group, beginTime is the offset from the beginning of its parent object — the animation group. So if beginTime of the animation is 5, it begins 5 seconds after the animation group begins.

If an animation is added directly to a layer, beginTime is still the offset from the beginning of its parent object — the layer. But since the beginning of a layer is in the past1, I can not simply set beginTime to 5 to delay the animation 5 seconds, because 5 seconds after the beginning of a layer is probably still a past time. What I usually really want is a delay relative to when the animation is added to the layer — denoted by addTime.

This is the code from the question with an addition of logs:

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

Output:

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.

To sum it all up,

The animation duration is a total of 10, I stopped the animation at 5 and I also want it to be my beginTime when I resume the animation.

So, I save the pause time and subtract it from my current time in order to get the relative animation time that has passed.



来源:https://stackoverflow.com/questions/20946481/comprehend-pause-and-resume-animation-on-a-layer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!