Как связать различные CAAnimation в приложении iOS
Мне нужно связать анимации, CABasicAnimation или CAAnimationGroup, но я не знаю, как это сделать, единственное, что я делаю, это то, что все анимации выполняются одновременно для одного и того же слоя.
Как я могу это сделать?
Например, слой с его содержимым, установленным на изображение автомобиля:
1st: переместите X точек вправо
2nd: поверните 90ª влево
3rd: Move X point
4th: масштабируйте слой
Все эти анимации должны выполняться по-второму, но я не могу этого сделать: S
Кстати: я не английский, извините, если я допустил некоторые ошибки в своей грамматике: D
Ответы
Ответ 1
tl; dr: Вам нужно вручную добавить каждую анимацию после предыдущих финишей.
Нет встроенного способа добавления последовательных анимаций. Вы можете установить задержку каждой анимации как сумму всех предыдущих анимаций, но я бы не рекомендовал ее.
Вместо этого я создам все анимации и добавлю их в изменяемый массив (используя массив как очередь) в том порядке, в котором они должны выполняться. Затем, установив себя как делегат анимации для всех анимаций, вы можете получить обратный вызов animationDidStop:finished:
всякий раз, когда заканчивается анимация.
В этом методе вы удалите из массива первую анимацию (что означает следующую анимацию) и добавьте ее в слой. Поскольку вы являетесь делегатом, вы получите вторую анимацию, когда она закончится, и в этом случае обратный вызов animationDidStop:finished:
снова запустится, а следующая анимация будет удалена из изменяемого массива и добавлена в этот слой.
Когда массив анимаций пуст, все анимации будут запущены.
Некоторые примеры кода, чтобы вы начали. Сначала вы настроили все свои анимации:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
[animation setToValue:(id)[[UIColor redColor] CGColor]];
[animation setDuration:1.5];
[animation setDelegate:self];
[animation setValue:[view layer] forKey:@"layerToApplyAnimationTo"];
// Configure other animations the same way ...
[self setSequenceOfAnimations:[NSMutableArray arrayWithArray: @[ animation, animation1, animation2, animation3, animation4, animation5 ] ]];
// Start the chain of animations by adding the "next" (the first) animation
[self applyNextAnimation];
Затем в обратном вызове делегата вы просто снова применяете следующую анимацию
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished {
[self applyNextAnimation];
}
- (void)applyNextAnimation {
// Finish when there are no more animations to run
if ([[self sequenceOfAnimations] count] == 0) return;
// Get the next animation and remove it from the "queue"
CAPropertyAnimation * nextAnimation = [[self sequenceOfAnimations] objectAtIndex:0];
[[self sequenceOfAnimations] removeObjectAtIndex:0];
// Get the layer and apply the animation
CALayer *layerToAnimate = [nextAnimation valueForKey:@"layerToApplyAnimationTo"];
[layerToAnimate addAnimation:nextAnimation forKey:nil];
}
Я использую настраиваемый ключ layerToApplyAnimationTo
, чтобы каждая анимация знала свой слой (он работает только под setValue:forKey:
и valueForKey:
).
Ответ 2
Что предлагает Дэвид, отлично работает, но я бы порекомендовал другой способ.
Если все ваши анимации находятся на одном уровне, вы можете создать группу анимации и сделать каждую анимацию другой beginTime
, где первая анимация начинается с beginTime
0, и каждая новая анимация начинается после общего продолжительность анимации раньше.
Если ваши анимации находятся на разных уровнях, вы не можете использовать группы анимации (все анимации в группе анимации должны действовать на одном уровне). В этом случае вам нужно представить отдельные анимации, каждая из которых имеет beginTime
, который смещен от CACurrentMediaTime()
, например:
CGFloat totalDuration = 0;
CABasicAnimation *animationOne = [CABasicAnimation animationWithKeyPath: @"alpha"];
animationOne.beginTime = CACurrentMediaTime(); //Start instantly.
animationOne.duration = animationOneDuration;
...
//add animation to layer
totalDuration += animationOneDuration;
CABasicAnimation *animationTwo = [CABasicAnimation animationWithKeyPath: @"position"];
animationTwo.beginTime = CACurrentMediaTime() + totalDuration; //Start after animation one.
animationTwo.duration = animationTwoDuration;
...
//add animation to layer
totalDuration += animationTwoDuration;
CABasicAnimation *animationThree = [CABasicAnimation animationWithKeyPath: @"position"];
animationThree.beginTime = CACurrentMediaTime() + totalDuration; //Start after animation three.
animationThree.duration = animationThreeDuration;
...
//add animation to layer
totalDuration += animationThreeDuration;
Ответ 3
Либо подход Дэвида, либо свойство beginTime подходят для цепочки анимации. См. http://wangling.me/2011/06/time-warp-in-animation.html - в нем разъясняется использование свойств beginTime и других CAMediaTiming.
Ответ 4
Здесь решение в Swift:
var animations = [CABasicAnimation]()
var animation1 = CABasicAnimation(keyPath: "key_path_1")
// animation set up here, I've included a few properties as an example
animation1.duration = 1.0
animation1.fromValue = 1
animation1.toValue = 0
animation1.append(animation1)
var animation2 = CABasicAnimation(keyPath: "key_path_2")
animation2.duration = 1.0
animation2.fromValue = 1
animation2.toValue = 0
// setting beginTime is the key to chaining these
animation2.beginTime = 1.0
animations.append(animation2)
let group = CAAnimationGroup()
group.duration = 2.0
group.repeatCount = FLT_MAX
group.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
group.animations = animations
yourLayer.addAnimation(group, forKey: nil)
Ответ 5
Используйте KVC. setValue для ключа для каждой анимации. После этого в animationDidStop вы можете определить, какая анимация была остановлена и запущена дальше в цепочке.
- (void)moveXrightAnimation {
CABasicAnimation* theAnimation = ...
[theAnimation setValue:@"movexright" forKey:@"animationID"];
//animation
}
- (void)rotate90leftAnimation {
CABasicAnimation* theAnimation = ...
[theAnimation setValue:@"rotate90left" forKey:@"animationID"];
//animation
}
- (void)moveXpointAnimation {
CABasicAnimation* theAnimation = ...
[theAnimation setValue:@"movexpoint" forKey:@"animationID"];
//animation
}
- (void)scaleAnimation {
CABasicAnimation* theAnimation = ...
[theAnimation setValue:@"scale" forKey:@"animationID"];
//animation
}
#pragma mark - CAAnimationDelegate
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
if([[anim valueForKey:@"animationID"] isEqual:@"movexright"]) {
[self rotate90leftAnimation];
}
if([[anim valueForKey:@"animationID"] isEqual:@"rotate90left"]) {
[self moveXpointAnimation];
}
if([[anim valueForKey:@"animationID"] isEqual:@"movexpoint"]) {
[self scaleAnimation];
}
}