Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comprehend pause and resume animation on a layer

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.

like image 217
KudoCC Avatar asked Jan 06 '14 09:01

KudoCC


2 Answers

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!

like image 56
KudoCC Avatar answered Oct 17 '22 19:10

KudoCC


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.

enter image description here

like image 44
Jeff.Lu Avatar answered Oct 17 '22 20:10

Jeff.Lu