Масштабирование мерцания iOS CAKeyFrameAnimation в конце анимации
В другом тесте анимации ключевых кадров я объединяю перемещение UIImageView (называется theImage
) по более беззаконному пути и масштабируя его по мере его движения, получая в 2 раза большее изображение в конце пути. Мой исходный код для этого содержит следующие элементы, чтобы запустить анимацию:
UIImageView* theImage = ....
float scaleFactor = 2.0;
....
theImage.center = destination;
theImage.transform = CGAffineTransformMakeScale(1.0,1.0);
CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size"];
[resizeAnimation setToValue:[NSValue valueWithCGSize:CGSizeMake(theImage.image.size.height*scaleFactor, theImage.image.size.width*scaleFactor)]];
resizeAnimation.fillMode = kCAFillModeBackwards;
resizeAnimation.removedOnCompletion = NO;
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
pathAnimation.path = [jdPath path].CGPath;
pathAnimation.fillMode = kCAFillModeBackwards;
pathAnimation.removedOnCompletion = NO;
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:pathAnimation, resizeAnimation, nil];
group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
group.removedOnCompletion = NO;
group.duration = duration;
group.delegate = self;
[theImage.layer addAnimation:group forKey:@"animateImage"];
Затем, когда анимация завершится, я хочу сохранить изображение в большем размере, поэтому я реализую:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
theImage.transform = CGAffineTransformMakeScale(scaleFactor,scaleFactor);
}
Это все работает.. вроде. Проблема в том, что в конце анимации theImage
мигает на короткое время - достаточно, чтобы это выглядело плохо. Я предполагаю, что это переход в конце анимации, где я установил преобразование в новый размер.
Экспериментируя с этим, я попробовал немного другую форму вышеупомянутого, но все еще получил то же самое мерцание:
CAKeyframeAnimation *resizeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
NSValue* startSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, 1.0, 1.0, 1.0)];
NSValue* endSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, scaleFactor, scaleFactor, 1.0)];
NSArray* sizeKeys = [NSArray arrayWithObjects:startSizeKey, endSizeKey, nil];
[resizeAnimation setValues:sizeKeys];
....
theImage.transform = CGAffineTransformMakeScale(scaleFactor,scaleFactor);
Но когда я закончил анимацию того же размера, что и оригинал, мерцания НЕТ:
CAKeyframeAnimation *resizeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
NSValue* startSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, 1.0, 1.0, 1.0)];
NSValue* middleSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, scaleFactor, scaleFactor, 1.0)];
NSValue* endSizeKey = [NSValue valueWithCATransform3D:CATransform3DScale (theImage.layer.transform, 1.0, 1.0, 1.0)];
NSArray* sizeKeys = [NSArray arrayWithObjects:startSizeKey, middleSizeKey, endSizeKey, nil];
[resizeAnimation setValues:sizeKeys];
....
theImage.transform = CGAffineTransformMakeScale(1.0,1.0);
Итак, мой главный вопрос: как я могу анимировать это изображение без мерцания и получить другой размер в конце анимации?
Изменить 2 марта
Мои первые тесты были с масштабированием изображения. Я просто попытался уменьшить его (IE scaleFactor = 0,4), и мерцание было намного более заметным и намного более очевидным в отношении того, что я вижу. Это была последовательность событий:
- Оригинальное изображение размера нарисовано на экране в начальной точке.
- Когда изображение движется по траектории, оно плавно сжимается.
- Полностью сжатое изображение прибывает в конец пути.
- Изображение тогда нарисовано в его оригинальном размере.
- Изображение окончательно раскрашивается в уменьшенный размер.
Так что, похоже, шаг 4 - это мерцание, которое я вижу.
Изменить 22 марта
Я только что загрузил на GitHub демонстрационный проект, который демонстрирует перемещение объекта по более беззаконному пути. Код можно найти в PathMove
Я также написал об этом в своем блоге в разделе "Перемещение объектов по безье в iOS".
3 ответа
Может быть сложно анимировать слой представления, используя Core Animation. Есть несколько вещей, которые делают его запутанным:
Установка анимации на слой не меняет свойств слоя. Вместо этого он изменяет свойства "слоя представления", который заменяет исходный "слой модели" на экране, пока применяется анимация.
Изменение свойства слоя обычно добавляет к слою неявную анимацию с именем свойства в качестве ключа анимации. Поэтому, если вы хотите явно анимировать свойство, вы обычно хотите установить его конечное значение, а затем добавить анимацию, ключом которой является имя свойства, чтобы переопределить неявную анимацию.
Представление обычно отключает неявную анимацию на своем слое. Он также портит свойства своего слоя другими довольно таинственными способами.
Кроме того, сбивает с толку то, что вы анимируете границы представления, чтобы увеличить его, но затем переключаетесь на масштабное преобразование в конце.
Я думаю, что самый простой способ сделать то, что вы хотите, это использовать UIView
Анимационные методы, насколько это возможно, включают только Core Animation для анимации ключевых кадров. Вы можете добавить анимацию ключевого кадра в слой представления после того, как UIView
добавьте свою собственную анимацию, и ваша ключевая анимация переопределит анимацию, добавленную UIView
,
Это сработало для меня:
- (IBAction)animate:(id)sender {
UIImageView* theImage = self.imageView;
CGFloat scaleFactor = 2;
NSTimeInterval duration = 1;
UIBezierPath *path = [self animationPathFromStartingPoint:theImage.center];
CGPoint destination = [path currentPoint];
[UIView animateWithDuration:duration animations:^{
// UIView will add animations for both of these changes.
theImage.transform = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
theImage.center = destination;
// Prepare my own keypath animation for the layer position.
// The layer position is the same as the view center.
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
positionAnimation.path = path.CGPath;
// Copy properties from UIView's animation.
CAAnimation *autoAnimation = [theImage.layer animationForKey:@"position"];
positionAnimation.duration = autoAnimation.duration;
positionAnimation.fillMode = autoAnimation.fillMode;
// Replace UIView's animation with my animation.
[theImage.layer addAnimation:positionAnimation forKey:positionAnimation.keyPath];
}];
}
На этой неделе я экспериментировал с некоторыми анимациями CAAnimation и заметил, что в конце моих анимаций мерцание. В частности, я бы изменил анимацию от круга к квадрату, а также изменил fillColor.
Каждая CAAnimation имеет свойство под названием removedOnCompletion
по умолчанию ДА. Это означает, что анимация исчезнет (т.е. переходы, масштабирование, вращение и т. Д.), Когда анимация завершится, и вы останетесь с исходным слоем.
Поскольку вы уже установили для своих свойств removeOnCompletion значение NO, я бы посоветовал попытаться перенести выполнение ваших анимаций на использование CATransactions вместо делегатов и animationDidStop...
[CATransaction begin];
[CATransaction setDisableActions:YES];
[CATransaction setCompletionBlock: ^{ theImage.transform = ...}];
// ... CAAnimation Stuff ... //
[CATransaction commit];
Вы создаете вызов блока завершения транзакции перед созданием анимации в соответствии с: http://zearfoss.wordpress.com/2011/02/24/core-animation-catransaction-protip/
Следующее из одного из моих методов:
[CATransaction begin];
CABasicAnimation *animation = ...;
animation.fromValue = ...;
animation.toValue = ...;
[CATransaction setCompletionBlock:^ { self.shadowRadius = _shadowRadius; }];
[self addAnimation:animation forKey:@"animateShadowOpacity"];
[CATransaction commit];
И я построил эту анимацию, и она отлично работает для меня без сбоев в конце: настройка и триггер - это пользовательские методы, которые есть в окне, и я запускаю анимацию в mousedown.
UIImageView *imgView;
UIBezierPath *animationPath;
-(void)setup {
canvas = (C4View *)self.view;
imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"img256.png"]];
imgView.frame = CGRectMake(0, 0, 128, 128);
imgView.center = CGPointMake(384, 128);
[canvas addSubview:imgView];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[UIImageView animateWithDuration:2.0f animations:^{
[CATransaction begin];
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
pathAnimation.duration = 2.0f;
pathAnimation.calculationMode = kCAAnimationPaced;
animationPath = [UIBezierPath bezierPath];
[animationPath moveToPoint:imgView.center];
[animationPath addLineToPoint:CGPointMake(128, 512)];
[animationPath addLineToPoint:CGPointMake(384, 896)];
pathAnimation.path = animationPath.CGPath;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
[imgView.layer addAnimation:pathAnimation forKey:@"animatePosition"];
[CATransaction commit];
CGFloat scaleFactor = 2.0f;
CGRect newFrame = imgView.frame;
newFrame.size.width *= scaleFactor;
newFrame.size.height *= scaleFactor;
newFrame.origin = CGPointMake(256, 0);
imgView.frame = newFrame;
imgView.transform = CGAffineTransformRotate(imgView.transform,90.0*M_PI/180);
}];
}
CAAnimation
s будет мерцать в конце, если состояние терминала было назначено таким образом, что оно само создавало неявную анимацию. Иметь ввиду CAAnimation
s - это временные корректировки свойств объекта для визуализации перехода. Когда анимация завершится, если состояние слоя все еще остается исходным начальным состоянием, это то, что будет отображаться временно до тех пор, пока вы не установите окончательное состояние слоя, которое вы делаете в своем animationDidStop:
метод.
Кроме того, ваша анимация регулирует bounds.size
свойство вашего слоя, поэтому вы должны аналогичным образом установить конечное состояние, а не использовать настройку преобразования в качестве конечного состояния. Вы также можете использовать transform
свойство как анимационное свойство в анимации вместо bounds.size
,
Чтобы исправить это, сразу же после назначения анимации измените состояние проницаемости слоя на желаемое состояние терминала, чтобы после завершения анимации не было мерцания, но сделайте это таким образом, чтобы не запускать неявную анимацию до начала анимации. В частности, в вашем случае вы должны сделать это в конце вашей анимации:
UIImageView* theImage = ....
float scaleFactor = 2.0;
....
theImage.center = destination;
theImage.transform = CGAffineTransformMakeScale(1.0,1.0);
CGSize finalSize = CGSizeMake(theImage.image.size.height*scaleFactor, theImage.image.size.width*scaleFactor);
CABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size"];
[resizeAnimation setToValue:[NSValue valueWithCGSize:finalSize]];
resizeAnimation.fillMode = kCAFillModeBackwards;
resizeAnimation.removedOnCompletion = NO;
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
pathAnimation.path = [jdPath path].CGPath;
pathAnimation.fillMode = kCAFillModeBackwards;
pathAnimation.removedOnCompletion = NO;
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:pathAnimation, resizeAnimation, nil];
group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
group.removedOnCompletion = NO;
group.duration = duration;
group.delegate = self;
[theImage.layer addAnimation:group forKey:@"animateImage"];
[CATransaction begin];
[CATransaction setDisableActions:YES];
theImage.bounds = CGRectMake( theImage.bounds.origin.x, theImage.bounds.origin.y, finalSize.width, finalSize.height );
[CATransaction commit];
а затем удалите настройку преобразования в вашем animationDidStop:
метод.