objective-c - framework - swift ios documentation
Encadenamiento de animaciones de Core Animation (6)
¿Cuál es la forma más elegante y modular de encadenar animación en un contexto de Animación principal ?
Me refiero a hacer animaciones que comienzan justo cuando otras terminan (por ejemplo, cambiar la position
y luego la opacity
). El enfoque normal es cambiar directamente las propiedades:
layer.position = new_point;
layer.opacity = 0.0f;
pero esto los hará al mismo tiempo. Quiero hacer que uno espere al otro.
¿Y qué hay de encadenar animaciones para diferentes objetos? He leído sobre CATransaction
usado como:
[CATransaction begin]
layer1.property = new_property;
[CATransaction begin]
layer2.property2 = new_property2;
[CATransaction commit];
[CATransaction commit];
pero parece que no funciona ...
Como señaló Matt, puede crear grupos de animación que consisten en diferentes animaciones para la misma capa con diferentes tiempos de inicio. También puede establecer un delegado para objetos de CAAnimation
autónomos o CAAnimation
Groups y, a medida que finaliza cada animación, llamará a animationDidStop:finished:
delegate method (tenga en cuenta que las animaciones que forman parte de un grupo no invocarán la animationDidStop:finished:
sus delegadosDidStop animationDidStop:finished:
método.
Descubrí un truco genial que hace que usar el CAAnimation animationDidStop:finished:
más poderoso. Utilizo el método setValue:forKey:
para agregar un bloque de código a una animación o grupo de animación independiente, con la clave @ "animationCompletionBlock". Luego escribo un método general animationDidStop:finished:
que comprueba la animación recién completada para una clave @ "animationCompletionBlock", y si la encuentra, ejecuta allí el bloque de código.
Echa un vistazo a este proyecto en github para obtener un ejemplo práctico de esa técnica:
Demostración de CAAnimation con bloques de finalización
Usted también establece un grupo de animaciones dentro de un
[CATransaction begin];
//...
[[CATransaction commit];
bloquear, como sugirió. Cuando lo haces, puedes usar el método de clase setCompletionBlock:
para invocar un bloque de código cuando se completan todas las animaciones en el grupo de transacciones actual. El bloque de finalización para una transacción puede desencadenar la siguiente transacción.
Hago esto usando el método setCompletionBlock
para definir un cierre que desencadena la próxima animación cuando termina la primera:
[CATransaction begin]
layer1.property = new_property;
CATransaction.setCompletionBlock {
[CATransaction begin]
layer2.property2 = new_property2;
[CATransaction commit];
}
[CATransaction commit];
Lo que siempre he preferido establecer por separado el tiempo de inicio y fin de cada animación es este:
Utilicé A2DynamicDelegate (cuyo desarrollo ahora está sucediendo en BlocksKit -Repo, quién sabe por qué <_ <) para implementar una propiedad completionBlock en una Categoría en CAAnimation.
Esto me permitió hacer cosas como esta:
CAAnimation *a = ...
CAAnimation *b = ...
CAAnimation *c = ...
a.completionHandler = ^{
[self.layer addAnimation:b forKey:@"foo"];
[self.layer addAnimation:c forKey:@"bar"];
};
Mucho más flexible :)
He subido mi código para el controlador de finalización here . Sin embargo, eche un vistazo a la notificación en el archivo de encabezado. Estoy realmente confundido por qué no se llama el método.
No creo que puedas "anidar" animaciones de CA como lo has hecho en tu ejemplo.
Debe especificar un delegado para la animación y colocar su segunda "transición" dentro de la animationDidStop:finished:
selector del delegado.
Es posible que desee echar un vistazo a la Guía de programación de tipos y tiempos de animación de Apple.
Sin incluir todos los "trucos" en mi "cadena de herramientas", este ejemplo no es directamente copy / pastable ... pero sí muestra una estrategia REALMENTE fácil para animaciones "encadenadas".
CATransform3D trans = m34(); // define chain-wide constants.
// Name your "stack". My "nextObject" returns arr[i]->done == nil.
NSArray *layerStack = layer.sublayers;
//define a block, that "takes" a layer as it''s argument.
void(^__block ChainBlock)(CALayer*) = ^(CALayer *m) {
// animations, transforms, etc for each inividual "step".
[m animate:@"transform"
// These are just NSValue-wrapped CAT3D''s
from:AZV3d(CATransform3DRotate(trans, 0,1,0,0))
to:AZV3d(CATransform3DRotate(trans,1.5,1,0,0))
time:2 // total time == each step * layerStack.count
eased:kCAMediaTimingFunctionEaseOut
completion:^{ // In completion, look for "next" layer.
CAL* m2 = [layers nextObject];
// If there is "another" layer, call this block, again... with it.
if (m2) chainAnis(m2);
// Otherise,you''re done. Cleanup, toggle values, whatevs.
else self.finishedProperty = YES;
}];
};
// Give the block we just defined your "first" layer.
ChainBlock(layerStack[0]); // It will recursively feed itself.
Esto obviamente depende de alguna "magia externa", pero el concepto es simple y elimina (a través de dependencias) la necesidad de "tratar con" CUALQUIER tipo de delegación grosera. En particular, las categorías animate:from:to:time:easing:completion
, etc. provienen del gran FunSize Framework, en Github .
También puede usar la agrupación de animación y usar el campo beginTime de la animación. Pruebe algo como esto:
CABasicAnimation *posAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
[posAnimation setFromValue:[NSNumber numberWithFloat:0.0]];
[posAnimation setToValue:[NSNumber numberWithFloat:1.0]];
// Here''s the important part
[posAnimation setDuration:10.0];
[posAnimation setBeginTime:0.0];
CABasicAnimation *borderWidthAnimation = [CABasicAnimation animationWithKeyPath:@"borderWidth"];
[borderWidthAnimation setFromValue:[NSNumber numberWithFloat:0.0]];
[borderWidthAnimation setToValue:[NSNumber numberWithFloat:1.0]];
// Here''s the important part
[borderWidthAnimation setDuration:10.0];
[borderWidthAnimation setBeginTime:5.0];
CAAnimationGroup *group = [CAAnimationGroup animation];
[group setDuration:10.0];
[group setAnimations:[NSArray arrayWithObjects:posAnimation, borderWidthAnimation, nil]];
[layer addAnimation:group forKey:nil];
Tenga en cuenta que la duración de toda la animación es de 10 segundos. El primero comienza en el segundo 0 y el segundo comienza en 5 segundos.