Объединение анимаций Core Animation

Какой самый элегантный и модульный способ связать анимацию в контексте Core Animation?

Я хочу делать анимацию, которая начинается сразу после завершения других (например, изменение position, а затем opacity) .. нормальный подход заключается в прямом изменении свойств:

layer.position = new_point;
layer.opacity = 0.0f;

но это сделает их одновременно. Я хочу заставить одного ждать другого.

А как насчет объединения анимаций для разных объектов? Я читал о CATransaction, например:

[CATransaction begin]
layer1.property = new_property;
[CATransaction begin]
layer2.property2 = new_property2;
[CATransaction commit];
[CATransaction commit];

но вроде не работает ..


person Jack    schedule 15.12.2009    source источник


Ответы (6)


Вы также можете использовать группировку анимации и использовать поле beginTime анимации. Попробуйте что-то вроде этого:

CABasicAnimation *posAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
[posAnimation setFromValue:[NSNumber numberWithFloat:0.0]];
[posAnimation setToValue:[NSNumber numberWithFloat:1.0]];
// Here's the important part
[posAnimation setDuration:10.0];
[posAnimation setBeginTime:0.0];

CABasicAnimation *borderWidthAnimation = [CABasicAnimation animationWithKeyPath:@"borderWidth"];
[borderWidthAnimation setFromValue:[NSNumber numberWithFloat:0.0]];
[borderWidthAnimation setToValue:[NSNumber numberWithFloat:1.0]];
// Here's the important part
[borderWidthAnimation setDuration:10.0];
[borderWidthAnimation setBeginTime:5.0];

CAAnimationGroup *group = [CAAnimationGroup animation];
[group setDuration:10.0];
[group setAnimations:[NSArray arrayWithObjects:posAnimation, borderWidthAnimation, nil]];

[layer addAnimation:group forKey:nil];

Обратите внимание, что продолжительность всей анимации составляет 10 секунд. Первый начинается со второго 0, а второй начинается с 5 секунд.

person Matt Long    schedule 16.12.2009
comment
обратите внимание, что это замечательно, но будет работать только для анимации, примененной к одному слою. - person nielsbot; 16.07.2012
comment
Также, если вы хотите вызвать метод сразу после завершения первой анимации, вы не можете этого сделать. animationDidStop вызывается только тогда, когда вся группа завершена. - person durazno; 11.04.2016
comment
путь ключа "position" - это CGPoint, который необходимо закодировать с помощью NSValue (а не NSNumber). - person wcochran; 14.04.2017

Как указал Мэтт, вы можете создавать группы анимации, которые состоят из разных анимаций для одного и того же слоя с разным временем начала. Вы также можете установить делегата для автономных CAAnimation объектов или CAAnimation групп, и по завершении каждой анимации он будет вызывать animationDidStop:finished: метод делегата (обратите внимание, что анимации, которые являются частью группы, не будут вызывать метод animationDidStop:finished: их делегата.

Я придумал крутой трюк, который делает использование метода CAAnimation animationDidStop:finished: более эффективным. Я использую метод setValue:forKey: для добавления блока кода в отдельную анимацию или группу анимаций с помощью ключа @ "animationCompletionBlock". Затем я пишу общий animationDidStop:finished: метод, который проверяет только что завершенную анимацию на наличие ключа @ "animationCompletionBlock" и, если он его находит, выполняет там блок кода.

Взгляните на этот проект на github, чтобы увидеть рабочий пример этой техники:

Демонстрация CAAnimation с блоками завершения

Вы также можете установить группу анимаций внутри

[CATransaction begin];
//...
[[CATransaction commit];

заблокировать, как вы предложили. Когда вы это сделаете, вы можете использовать CATransaction метод класса setCompletionBlock: для вызова блока кода после завершения всех анимаций в текущей группе транзакций. Затем блок завершения для одной транзакции может инициировать следующую транзакцию.

person Duncan C    schedule 19.02.2013

Я выполняю это, используя метод setCompletionBlock, чтобы определить закрытие, которое запускает следующую анимацию, когда первая закончится:

[CATransaction begin]
layer1.property = new_property;
CATransaction.setCompletionBlock {
    [CATransaction begin]
    layer2.property2 = new_property2;
    [CATransaction commit];
}
[CATransaction commit];
person wcochran    schedule 14.04.2017

Я не верю, что вы можете «вкладывать» CA-анимации, как в вашем примере.

Вам необходимо указать делегата для анимации и поместить второй «переход» в animationDidStop:finished: селектор делегата.

Возможно, вы захотите взглянуть на типы анимации Apple. & Руководство по программированию времени.

person Meltemi    schedule 15.12.2009

Я всегда предпочитал устанавливать время начала и окончания каждой анимации по отдельности:

Я использовал A2DynamicDelegate (чья разработка сейчас происходит в BlocksKit -Repo, кто знает почему _ ‹‹), чтобы реализовать свойство завершенияBlock в категории на CAAnimation.

Это позволило мне делать такие вещи:

CAAnimation *a = ...
CAAnimation *b = ...
CAAnimation *c = ...

a.completionHandler = ^{
    [self.layer addAnimation:b forKey:@"foo"];
    [self.layer addAnimation:c forKey:@"bar"];
};

Намного гибче :)

Я загрузил свой код для обработчика завершения сюда. Обратите внимание на уведомление в заголовочном файле. Я действительно не понимаю, почему метод не вызывается.

person Ahti    schedule 19.02.2013

Без включения всех «трюков» в мою «цепочку инструментов», этот пример нельзя напрямую копировать / вставлять… но он показывает ДЕЙСТВИТЕЛЬНО простую стратегию для «связанных» анимаций.

CATransform3D trans = m34();  // define chain-wide constants.
// Name your "stack". My "nextObject" returns arr[i]->done == nil. 
NSArray *layerStack = layer.sublayers;
//define a block, that "takes" a layer as it's argument.
void(^__block ChainBlock)(CALayer*) = ^(CALayer *m) { 
// animations, transforms, etc for each inividual "step".
    [m animate:@"transform" 
          // These are just NSValue-wrapped CAT3D's
          from:AZV3d(CATransform3DRotate(trans,  0,1,0,0)) 
            to:AZV3d(CATransform3DRotate(trans,1.5,1,0,0)) 
          time:2    // total time == each step * layerStack.count
         eased:kCAMediaTimingFunctionEaseOut
    completion:^{   // In completion, look for "next" layer.
                    CAL*  m2 = [layers nextObject]; 
// If there is "another" layer, call this block, again... with it.
                      if (m2)  chainAnis(m2);
// Otherise,you're done.  Cleanup, toggle values, whatevs.
                     else self.finishedProperty = YES;
    }];
};
// Give the block we just defined your "first" layer.
ChainBlock(layerStack[0]);  // It will recursively feed itself.

Это, очевидно, зависит от некоторой «внешней магии», но концепция проста и устраняет (через зависимости) необходимость «иметь дело» с ЛЮБОЙ разновидностью грубого делегирования. В частности, animate:from:to:time:easing:completion и т. Д. категории взяты из великолепного FunSize Framework на Github.

person Alex Gray    schedule 06.05.2013