Цепи анимации анимации ядра
Какой самый элегантный и модульный способ анимации в анимации в контексте 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];
но он, похоже, не работает.
Ответы
Ответ 1
Вы также можете использовать группировку анимации и использовать поле beginTime анимации. Попробуйте что-то вроде этого:
CABasicAnimation *posAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
[posAnimation setFromValue:[NSNumber numberWithFloat:0.0]];
[posAnimation setToValue:[NSNumber numberWithFloat:1.0]];
// Here 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 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 секунд.
Ответ 2
Как отметил Мэтт, вы можете создавать анимационные группы, которые состоят из разных анимаций для одного и того же слоя с разными временами начала. Вы также можете установить делегат для автономных объектов CAAnimation
или групп CAAnimation
, и по мере окончания каждой анимации он вызовет метод делегирования animationDidStop:finished:
(обратите внимание, что анимации, входящие в группу, не будут вызывать их делегат animationDidStop:finished:
.
Я понял, какой классный трюк использует метод CAAnimation animationDidStop:finished:
более мощный. Я использую метод setValue:forKey:
, чтобы добавить блок кода в отдельную анимационную или анимационную группу с ключом @ "animationCompletionBlock". Затем я пишу общий метод animationDidStop:finished:
, который проверяет только что завершенную анимацию для ключа @ "animationCompletionBlock", и если он ее найдет, выполните там блок кода.
Взгляните на этот проект на github для рабочего примера этого метода:
демо CAAnimation с блоками завершения
Вы также установите группу анимаций внутри
[CATransaction begin];
//...
[[CATransaction commit];
как вы сказали. Когда вы это сделаете, вы можете использовать метод класса CATransaction
setCompletionBlock:
для вызова блока кода, когда все анимации в текущей группе транзакций завершены. Блок завершения для одной транзакции может затем инициировать следующую транзакцию.
Ответ 3
Я не верю, что вы можете "вложить" анимации CA, как в вашем примере.
Вам нужно указать делегат для анимации и поместить второй "переход" в селектор animationDidStop:finished:
делегата.
Возможно, вы хотите взглянуть на Apple Типы анимации и руководство по программированию по времени.
Ответ 4
То, что я всегда предпочитал устанавливать время начала и окончания каждой анимации отдельно, это:
Я использовал A2DynamicDelegate (развитие которого сейчас происходит в BlocksKit -Repo, кто знает, почему < _ <), чтобы реализовать свойство завершения блокировки в категории по CAAnimation.
Это позволило мне сделать так:
CAAnimation *a = ...
CAAnimation *b = ...
CAAnimation *c = ...
a.completionHandler = ^{
[self.layer addAnimation:b forKey:@"foo"];
[self.layer addAnimation:c forKey:@"bar"];
};
Гораздо более гибкий:)
Я загрузил свой код для обработчика завершения здесь. Однако обратите внимание на уведомление в файле заголовка. Я действительно смущен, почему метод не вызывается.
Ответ 5
Я вытащил это, используя метод setCompletionBlock
, чтобы определить замыкание, которое запускает следующую анимацию, когда первая завершена:
[CATransaction begin]
layer1.property = new_property;
CATransaction.setCompletionBlock {
[CATransaction begin]
layer2.property2 = new_property2;
[CATransaction commit];
}
[CATransaction commit];
Ответ 6
Без включения всех "трюков" в мою "инструментальную цепочку" этот пример не копируется напрямую/скоропортящийся... но он показывает ДЕЙСТВИТЕЛЬНО легкую стратегию "прикованных" анимаций.
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 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.