Лучший способ выполнить несколько последовательных анимаций UIView?
У меня есть серия из 8 анимаций UIView, которые появляются непосредственно после загрузки моего представления. Прямо сейчас я выполняю это, используя метод делегата animationDidStop:finished:context
, и все работает так, как ожидалось. Проблема в том, что у меня есть новый метод для каждой анимации. Большая часть кода в каждом из этих методов повторяется, только с длительностью анимации и фактическим позиционированием элементов.
Я попытался создать единый метод, который будет вызываться, и иметь контекст, содержащий параметры, необходимые для правильного изменения интерфейса, но он, похоже, рекурсивно называет себя намного больше, чем я его называю:
-(void)animationDidStop:(NSString *)animationID finished:(BOOL)finished context:(void *)context{
NSNumber *number = (NSNumber *)context;
int animationStep = [number intValue];
int nextAnimationStep = animationStep + 1;
NSNumber *nextAnimationStepNumber = [NSNumber numberWithInt:nextAnimationStep];
NSLog(@"Animation Step: %i", animationStep);
CGRect firstFrame = CGRectMake(self.feedsScroll.frame.size.width * 2, 0.0f, self.secondFeedView.view.frame.size.width, self.secondFeedView.view.frame.size.height);
CGRect thirdFrame = CGRectMake(self.feedsScroll.frame.size.width * 2, 0.0f, self.thirdFeedView.view.frame.size.width, self.thirdFeedView.view.frame.size.height);
[UIView beginAnimations:nil context:nextAnimationStepNumber];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationDelegate:self];
if (animationStep < 8)
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
NSLog(@"Beginning animations");
switch (animationStep) {
case 0:
[UIView setAnimationDuration:.3];
self.firstFeedView.view.center = CGPointMake(self.firstFeedView.view.center.x + 30, self.firstFeedView.view.center.y);
break;
case 1:
[UIView setAnimationDuration:.3];
self.firstFeedView.view.center = CGPointMake(self.firstFeedView.view.center.x - 30, self.firstFeedView.view.center.y);
break;
case 2:
[UIView setAnimationDuration:.3];
[self.secondFeedView.view setFrame:firstFrame];
break;
case 3:
[UIView setAnimationDuration:.3];
self.secondFeedView.view.center = CGPointMake(self.secondFeedView.view.center.x + 30, self.firstFeedView.view.center.y);
break;
case 4:
[UIView setAnimationDuration:.3];
self.secondFeedView.view.center = CGPointMake(self.secondFeedView.view.center.x - 30, self.firstFeedView.view.center.y);
break;
case 5:
NSLog(@"Animation step 6");
[UIView setAnimationDuration:.5];
self.firstFeedView.view.center = CGPointMake(self.firstFeedView.view.center.x - 230, self.firstFeedView.view.center.y);
self.secondFeedView.view.center = CGPointMake(self.secondFeedView.view.center.x - 230, self.firstFeedView.view.center.y);
break;
case 6:
[UIView setAnimationDuration:.5];
[self.thirdFeedView.view setFrame:thirdFrame];
break;
case 7:
[UIView setAnimationDuration:.3];
self.thirdFeedView.view.center = CGPointMake(self.thirdFeedView.view.center.x + 30, self.firstFeedView.view.center.y);
break;
case 8:
[UIView setAnimationDuration:.3];
self.thirdFeedView.view.center = CGPointMake(self.thirdFeedView.view.center.x - 30, self.thirdFeedView.view.center.y);
break;
default:
break;
}
[UIView commitAnimations];
}
Я знаю, что это, вероятно, наивная реализация. Я новичок в разработке iPhone, и я ищу некоторые рекомендации, которые можно применить здесь. Я иду об этом не так?
Ответы
Ответ 1
Если вы захотите подойти к iOS 4.0, новый подход к анимации на основе блоков может сделать эту анимацию тривиальной. Например:
[UIView animateWithDuration:1.0 animations:^{ view.position = CGPointMake(0.0f, 0.0f); } completion:^(BOOL finished){
[UIView animateWithDuration:0.2 animations:^{ view.alpha = 0.0; } completion:^(BOOL finished){
[view removeFromSuperview]; }];
}]
приведет к тому, что view
будет анимировать (0, 0) в течение 1 секунды, исчезнет на 0,2 секунды, а затем будет удалено из его супервизора. Эти анимации будут последовательными.
Первый блок в методе класса +animateWithDuration:animations:completion:
содержит изменения свойств для анимации сразу, а второй - действие, которое необходимо выполнить при завершении анимации. Поскольку этот обратный вызов может содержать другую анимацию такого типа, вы можете вложить их в произвольные цепочки анимаций.
Ответ 2
Вы можете использовать блоки для этой цели и получить очень чистый результат.
NSMutableArray* animationBlocks = [NSMutableArray new];
typedef void(^animationBlock)(BOOL);
// getNextAnimation
// removes the first block in the queue and returns it
animationBlock (^getNextAnimation)() = ^{
animationBlock block = (animationBlock)[animationBlocks firstObject];
if (block){
[animationBlocks removeObjectAtIndex:0];
return block;
}else{
return ^(BOOL finished){};
}
};
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
[UIView animateWithDuration:1.0 animations:^{
//...animation code...
} completion: getNextAnimation()];
}];
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
[UIView animateWithDuration:1.0 animations:^{
//...animation code...
} completion: getNextAnimation()];
}];
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
NSLog(@"Multi-step Animation Complete!");
}];
// execute the first block in the queue
getNextAnimation()(YES);
Взято из: http://xibxor.com/objective-c/uiview-animation-without-nested-hell/
Ответ 3
Мы создали компонент для целенаправленности шагов анимации с использованием блоков (CPAnimationSequence на Github). Мы описали мотивацию и обоснование в нашем блоге развития iOS.
Он дает вам очень читаемый код, что-то вроде этого:
[[CPAnimationSequence sequenceWithSteps:
[CPAnimationStep for:0.25 animate:^{ self.imageView.alpha = 0.0; }],
[CPAnimationStep for:0.25 animate:^{ self.headline.alpha = 0.0; }],
[CPAnimationStep for:0.25 animate:^{ self.content.alpha = 0.0; }],
[CPAnimationStep after:1.0 for:0.25 animate:^{ self.headline.alpha = 1.0; }],
[CPAnimationStep for:0.25 animate:^{ self.content.alpha = 1.0; }],
nil]
runAnimated:YES];
Ответ 4
"Контекст" - это пустота * и поэтому не сохраняется. Есть несколько вариантов:
- Сохраните его, когда вы его установили. Autorelease это в начале обратного вызова. Это кажется нехорошим (Objective-C функции должны соответственно сохраняться/сниматься, но, к сожалению, контекст не является
id
).
- Сохраните номер в строке:
int animationStep = [animationID intValue];
и [UIView beginAnimations:[NSString stringWithFormat:@"%d", nextAnimationStep] context:NULL];
. Это также неприятно.
- Предполагая, что UIKit/CoreAnimation не разыскивает указатель,
int animationStep = (int)context;
и [UIView beginAnimations:nil context:(void*)nextAnimationStep];
. Это нехорошо (и, вероятно, вызывает поведение undefined в соответствии со стандартом C).
В другом примечании finished:(BOOL)finished
должно быть finished:(NSNumber*)finished
. Они ошиблись в оригинальных документах; вероятно, было проще изменить документы, чтобы отразить API вместо изменения API и иметь магическую поддержку обратной совместимости (хотя передача bool более разумна).
Ответ 5
// create the view that will execute our animation
UIImageView* logo = [[UIImageView alloc] initWithFrame:self.view.frame];
// load all the frames of our animation
logo.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:@"frame_00001.png"],
[UIImage imageNamed:@"frame_00002.png"],
[UIImage imageNamed:@"frame_00003.png"],
[UIImage imageNamed:@"frame_00004.png"],
[UIImage imageNamed:@"frame_00005"], nil];
// all frames will execute in 3 seconds
logo.animationDuration =3.3;
// repeat the annimation forever
logo.animationRepeatCount = 1;
// start animating
[logo startAnimating];
// add the animation view to the main window
[self.view addSubview:logo];
[logo release];
поместите это в ViewDidLoad