Освежить паузу и возобновить анимацию на слое

Я изучаю анимацию в Core Animation Programming Guide, и я застрял в понимании паузы и возобновлении анимации на слое.

В документе рассказывается, как приостановить и возобновить анимацию без ясного объяснения. Я думаю, что ключ должен понять, что такое timeOffset и beginTime метод CAlayer.

Этот код является паузой и возобновляет анимацию. В методе 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;
}

Любая помощь будет оценена.

Ответ 1

В файле заголовка 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 - начало) * скорость + смещение

Эта формула определяет, как глобальное время (или родительское время, tp) отображается в локальное время слоя. И эта формула может объяснить все перечисленные коды:

  • В момент времени A анимация приостановлена. После установок скорость = 0 и timeOffset = pauseTime, местное время слоя равно pauseTime. И местное время больше не увеличивается, потому что скорость = 0;
  • В момент времени B анимация возобновляется. После установок скорость = 1.0, timeOffset = 0, beginTime = 0, локальное время слоя равно глобальному времени (или tp), которое равно (timePause + timeSinacePause). Но нам нужно, чтобы анимация начиналась с момента времени #A, поэтому мы устанавливаем startTime = timeSincePaused, а затем локальное время слоя равно timePause. Причина в том, что анимация продолжается с момента приостановления.

введите описание изображения здесь

Ответ 2

Пусть у нас есть тест на два свойства слоя: beginTime и timeOffset.

Необходимое условие

Мы получаем временное пространство CALayer с помощью [layer convertTime:CACurrentMediaTime() fromLayer:nil]

1, Назначить 5.0 на уровень beginTime (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

Результат показывает, что если я добавлю 5.0 на beginTime, время слоя будет минус 5.0. Если анимация находится в полете, добавление 5.0 на beginTime приведет к тому, что анимация повторит анимацию 5 секунд назад.

2, Назначить 5.0 для слоя timeOffset (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

Результат показывает, что если я добавлю 5.0 на timeOffset, время слоя добавит 5.0. Если анимация находится в полете, добавление 5.0 на timeOffset приведет к тому, что анимация скажется на анимацию, которая будет выполняться через 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]) ;

log показывает 2014-01-15 11:25:53.975 newUserInterface[1530:70b] CACurrentMediaTime:8727.108740, что означает, что анимация начнется с 8727 и остановится на 8727 + 10 в временном пространстве t1.layer.

Когда анимация находится в полете, и я приостанавливаю анимацию с помощью метода - (void)pauseLayer:(CALayer*)layer.

layer.speed = 0.0; приведет к тому, что уровень остановки и время слоя будут установлены равными 0. (Я знаю, потому что, когда для параметра layer.speed установлено значение 0, я сразу извлекаю время слоя и регистрирую его)

layer.timeOffset = pausedTime; добавит pauseTime к слою времени (при условии, что layer.timeOffset равен 0), теперь время слоя приостанавливается.

- (void)pauseLayer:(CALayer *)layer
{
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; // pauseTime is the time with respect to layer time space
    layer.speed = 0.0; // layer local time is 0
    layer.timeOffset = pausedTime; // layer 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 во время слоя, поэтому время слоя приостановлено.

Подождите немного, дайте краткое описание. layer.speed равен 0.0, 'layer.timeOffset' равен pausedTime, который равен 8727 + 1, а время слоя также приостанавливается. Пожалуйста, помните, мы будем использовать их в ближайшее время.

Продолжим, я возобновляю анимацию с 8727 + 11 с помощью метода resumeLayer, layer.speed = 1.0; он добавит 8727 + 11 во время слоя, поэтому время слоя равно 8727 + 1 + 8727 + 11, layer.timeOffset = 0.0; оно вызывает layer time минус 8727 + 1, потому что layer.timeOffset - 8727 + 1 до этого, локальное время слоя - 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;

Скажите правду, я до сих пор не знаю, как работает анимация, что я делаю, анализируя результат, это не очень хорошее решение. Я очень рад, что любой, у кого есть идеи по этому поводу, может поделиться. Спасибо!

Ответ 3

Из docs:

BeginTime

beginTime Задает время начала приемника по отношению к его родительский объект, если применимо. (Обязательно)

Здесь большое объяснение взято из здесь:

Если анимация находится в анимационной группе, 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 your begin time. When you hit resume you want to begin the animation from that relative time.

Чтобы подвести итог,

Продолжительность анимации - всего 10, я остановил анимацию на 5, и я также хочу, чтобы это было моим началом, когда я возобновляю анимацию.

Итак, я сохраняю время паузы и вычитаю его из моего текущего времени, чтобы получить относительное время анимации, которое прошло.