IOS CAKeyFrameAnimation Scaling Flickers al final de la animación
uiimageview (3)
En otra prueba de la animación Key Frame, estoy combinando mover UIImageView (llamada theImage
) a lo largo de una trayectoria bezier y escalarla a medida que se mueve, lo que da como resultado una imagen 2x más grande al final de la ruta. Mi código inicial para hacer esto tiene estos elementos para iniciar la animación:
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"];
Luego, cuando finalice la animación, quiero retener la imagen en un tamaño mayor, así que implemento:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
theImage.transform = CGAffineTransformMakeScale(scaleFactor,scaleFactor);
}
Todo esto funciona ... más o menos. El problema es que al final de la animación, la theImage
parpadea por un breve momento, solo lo suficiente para que se vea mal. Supongo que esta es la transición al final de la animación donde configuré la transformación al nuevo tamaño.
Al experimentar con esto, probé una forma ligeramente diferente de la anterior, pero obtuve el mismo parpadeo:
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);
Pero cuando terminé la animación con el mismo tamaño que el original, NO hubo parpadeo:
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);
Entonces mi gran pregunta es ¿cómo puedo animar esta imagen sin parpadeo y terminar con un tamaño diferente al final de la animación?
Editar 2 de marzo
Mis pruebas iniciales fueron con escalar la imagen. Solo traté de reducirlo (IE scaleFactor = 0.4) y el parpadeo fue mucho más visible, y mucho más obvio en cuanto a lo que estoy viendo. Esta fue la secuencia de eventos:
- La imagen de tamaño original está pintada en la pantalla en el lugar de inicio.
- A medida que la imagen se mueve a lo largo del camino, se contrae suavemente.
- La imagen totalmente contraída llega al final de la ruta.
- La imagen se pinta con su tamaño original.
- La imagen finalmente se pinta en su tamaño encogido.
Entonces parece ser el paso 4 que es el parpadeo que estoy viendo.
Editar 22 de marzo
Acabo de subir a GitHub un proyecto de demostración que muestra el movimiento de un objeto a lo largo de una ruta bezier. El código se puede encontrar en PathMove
También escribí sobre esto en mi blog en Moving objects along a bezier path in iOS
Estuve experimentando con algunas animaciones de CA esta semana y me di cuenta de que había un parpadeo al final de mis animaciones. En particular, haría una animación de un círculo a un cuadrado, mientras cambio el fillColor también.
Cada CAAnimation tiene una propiedad llamada removedOnCompletion
que por defecto es YES. Esto significa que la animación desaparecerá (es decir, transiciones, escalas, rotaciones, etc.) cuando finalice la animación y se quedará con la capa original.
Como ya ha configurado sus propiedades removedOnCompletion en NO, le sugiero que intente cambiar la ejecución de sus animaciones para usar CATransactions, en lugar de delegados y animationDidStop ...
[CATransaction begin];
[CATransaction setDisableActions:YES];
[CATransaction setCompletionBlock: ^{ theImage.transform = ...}];
// ... CAAnimation Stuff ... //
[CATransaction commit];
Ponga la llamada al bloque de finalización de la transacción antes de crear sus animaciones, según: http://zearfoss.wordpress.com/2011/02/24/core-animation-catransaction-protip/
Lo siguiente es de uno de mis métodos:
[CATransaction begin];
CABasicAnimation *animation = ...;
animation.fromValue = ...;
animation.toValue = ...;
[CATransaction setCompletionBlock:^ { self.shadowRadius = _shadowRadius; }];
[self addAnimation:animation forKey:@"animateShadowOpacity"];
[CATransaction commit];
Y, construí esta animación y funciona bien para mí sin problemas técnicos al final: la configuración y el desencadenador son métodos personalizados que tengo en una ventana, y desencadenar la animación en el 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 parpadeará al final si el estado del terminal fue asignado de tal manera que él mismo creó una animación implícita. Tenga en cuenta que CAAnimation
s son ajustes temporales de las propiedades de un objeto con el objetivo de visualizar la transición. Cuando finalice la animación, si el estado de la capa sigue siendo el estado de inicio original, eso es lo que se mostrará de forma temporal hasta que establezca el estado final de la capa, lo que hará en su método animationDidStop:
.
Además, su animación está ajustando la propiedad bounds.size
de su capa, por lo que debe establecer de manera similar su estado final en lugar de usar el ajuste de transformación como su estado final. También podría usar la propiedad de transform
como propiedad de animación en la animación en lugar de bounds.size
.
Para remediar esto, inmediatamente después de asignar la animación, cambie el estado permeante de la capa al estado de terminal deseado para que cuando la animación finalice no haya parpadeo, pero hágalo de tal manera que no active una animación implícita antes de que comience la animación. Específicamente, en tu caso deberías hacer esto al final de tu configuración de animación:
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];
y luego elimine el ajuste de transformación en su método animationDidStop:
Puede ser complicado animar la capa de una vista usando Core Animation. Hay varias cosas que lo hacen confuso:
Establecer una animación en una capa no cambia las propiedades de la capa. En su lugar, cambia las propiedades de una "capa de presentación" que reemplaza la "capa de modelo" original en la pantalla, siempre que se aplique la animación.
Cambiar la propiedad de una capa normalmente agrega una animación implícita a la capa, con el nombre de la propiedad como la clave de la animación. Por lo tanto, si desea animar explícitamente una propiedad, generalmente desea establecer la propiedad a su valor final, luego agregue una animación cuya clave sea el nombre de la propiedad, para anular la animación implícita.
Una vista normalmente desactiva las animaciones implícitas en su capa. También se revuelve con las propiedades de su capa en otras formas un tanto misteriosas.
Además, es confuso que anime los límites de la vista para ampliarla, pero luego cambie a una transformación de escala al final.
Creo que la forma más fácil de hacer lo que se desea es usar los métodos de animación UIView
tanto como sea posible, y solo incorporar Core Animation para la animación del fotograma clave. Puede agregar la animación de fotograma clave a la capa de la vista después de que haya dejado que UIView
agregue su propia animación, y su animación de fotograma clave anulará la animación agregada por UIView
.
Esto funcionó para mí:
- (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];
}];
}