Масштабирование мерцания iOS CAKeyFrameAnimation в конце анимации - PullRequest
6 голосов
/ 29 февраля 2012

В другом тесте анимации ключевых кадров я комбинирую перемещение объекта 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), и мерцание было намного более заметным и намного более очевидным в отношении того, что я вижу. Это была последовательность событий:

  1. Исходное изображение отображается на экране в начальной точке.
  2. Когда изображение движется по траектории, оно плавно сжимается.
  3. Полностью сжатое изображение появляется в конце пути.
  4. Изображение раскрашивается в исходный размер.
  5. Изображение окончательно раскрашено в уменьшенный размер.

Так что, похоже, шаг 4 - это мерцание, которое я вижу.


Редактировать 22 марта

Я только что загрузил на GitHub демонстрационный проект, который демонстрирует перемещение объекта по более безликой траектории. Код можно найти на PathMove

Я также писал об этом в своем блоге по адресу Перемещение объектов по более изящному пути в iOS

Ответы [ 3 ]

7 голосов
/ 04 марта 2012

Может быть сложно анимировать слой вида с помощью 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];
    }];
}
4 голосов
/ 03 марта 2012

Я экспериментировал с некоторыми CAAnimations на этой неделе и заметил, что в конце моей анимации мерцание. В частности, я бы изменил анимацию от круга к квадрату, а также изменил заливку.

У каждой CAAnimation есть свойство с именем removedOnCompletion, по умолчанию YES. Это означает, что анимация исчезнет (т.е. переходы, масштабирование, вращение и т. Д.), Когда анимация завершится, и вы останетесь с исходным слоем.

Так как вы уже установили для своих свойств 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);

    }];
}
3 голосов
/ 03 марта 2012

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:.

...