¿Cómo crear una función de aceleración personalizada con Core Animation?

Estoy animando un CALayer largo de un CGPath (QuadCurve) muy bien en iOS. Pero me gustaría utilizar una función de relajación más interesante que las pocas proporcionadas por Apple (EaseIn / EaseOut, etc.). Por ejemplo, un rebote o función elástica.

Estas cosas son posibles de hacer con MediaTimingFunction (bezier):

enter image description here

Pero me gustaría crear funciones de tiempo que son más complejas. El problema es que el tiempo de los medios parece requerir un bezier cúbico que no es lo suficientemente potente como para crear estos efectos:

http://wiki.sparrow-framework.org/_media/manual/transitions.png

El código para crear lo anterior es lo suficientemente simple en otros marcos, lo que hace que esto sea muy frustrante. Tenga en cuenta que las curvas están mapeando el tiempo de entrada al tiempo de salida (curva Tt) y no las curvas de tiempo-posición. Por ejemplo, easeOutBounce (T) = t devuelve una nueva t . Entonces esa t se usa para trazar el movimiento (o cualquier propiedad que deberíamos animar).

Entonces, me gustaría crear una compleja CAMediaTimingFunction personalizada, pero no tengo ni idea de cómo hacerlo, o si es posible. ¿Hay alguna alternativa?

EDITAR:

Aquí hay un ejemplo concreto en los pasos. Muy educativo 🙂

  1. Quiero animar un objeto a lo largo de una línea desde el punto a hasta b , pero quiero que “rebote” su movimiento a lo largo de la línea usando la curva easeOutBounce de arriba. Esto significa que seguirá la línea exacta de la a a la b , pero se acelerará y desacelerará de una manera más compleja que lo que es posible usando la CAMediaTimingFunction actual basada en bielas.

  2. Hagamos de esa línea cualquier movimiento de curva arbitrario especificado con CGPath. Todavía debería moverse a lo largo de esa curva, pero debería acelerarse y desacelerarse de la misma manera que en el ejemplo de la línea.

En teoría, creo que debería funcionar así:

Vamos a describir la curva de movimiento como un movimiento de animación de fotogtwig clave (t) = p , donde t es el tiempo [0..1], p es la posición calculada en el tiempo t . Entonces move (0) devuelve la posición al comienzo de la curva, mueve (0.5) el centro exacto y mueve (1) al final. Usar un tiempo de función de temporización (T) = t para proporcionar los valores de t para mover debería darme lo que quiero. Para un efecto de rebote, la función de tiempo debe devolver los mismos valores t para el tiempo (0.8) y el tiempo (0.8) (solo un ejemplo). Simplemente reemplace la función de tiempo para obtener un efecto diferente.

(Sí, es posible hacer saltos de línea creando y uniendo cuatro segmentos de línea que van y vienen, pero eso no debería ser necesario. Después de todo, es simplemente una función lineal simple que mapea los valores de tiempo a las posiciones).

Espero que tenga sentido aquí.

Encontré esto:

Cocoa con amor – Curvas de aceleración paramétrica en Core Animation

Pero creo que se puede hacer un poco más simple y más legible usando bloques. Entonces, podemos definir una categoría en CAKeyframeAnimation que se parece a esto:

CAKeyframeAnimation + Parametric.h:

 // this should be a function that takes a time value between // 0.0 and 1.0 (where 0.0 is the beginning of the animation // and 1.0 is the end) and returns a scale factor where 0.0 // would produce the starting value and 1.0 would produce the // ending value typedef double (^KeyframeParametricBlock)(double); @interface CAKeyframeAnimation (Parametric) + (id)animationWithKeyPath:(NSString *)path function:(KeyframeParametricBlock)block fromValue:(double)fromValue toValue:(double)toValue; 

CAKeyframeAnimation + Parametric.m:

 @implementation CAKeyframeAnimation (Parametric) + (id)animationWithKeyPath:(NSString *)path function:(KeyframeParametricBlock)block fromValue:(double)fromValue toValue:(double)toValue { // get a keyframe animation to set up CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:path]; // break the time into steps // (the more steps, the smoother the animation) NSUInteger steps = 100; NSMutableArray *values = [NSMutableArray arrayWithCapacity:steps]; double time = 0.0; double timeStep = 1.0 / (double)(steps - 1); for(NSUInteger i = 0; i < steps; i++) { double value = fromValue + (block(time) * (toValue - fromValue)); [values addObject:[NSNumber numberWithDouble:value]]; time += timeStep; } // we want linear animation between keyframes, with equal time steps animation.calculationMode = kCAAnimationLinear; // set keyframes and we're done [animation setValues:values]; return(animation); } @end 

Ahora el uso se verá algo así:

 // define a parametric function KeyframeParametricBlock function = ^double(double time) { return(1.0 - pow((1.0 - time), 2.0)); }; if (layer) { [CATransaction begin]; [CATransaction setValue:[NSNumber numberWithFloat:2.5] forKey:kCATransactionAnimationDuration]; // make an animation CAAnimation *drop = [CAKeyframeAnimation animationWithKeyPath:@"position.y" function:function fromValue:30.0 toValue:450.0]; // use it [layer addAnimation:drop forKey:@"position"]; [CATransaction commit]; } 

Sé que podría no ser tan simple como lo que querías, pero es un comienzo.

Desde iOS 10, fue posible crear una función de temporización personalizada más fácil utilizando dos nuevos objetos de temporización.

1) UICubicTimingParameters permite definir la curva Bézier cúbica como una función de relajación.

 let cubicTimingParameters = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.25, y: 0.1), controlPoint2: CGPoint(x: 0.25, y: 1)) let animator = UIViewPropertyAnimator(duration: 0.3, timingParameters: cubicTimingParameters) 

o simplemente usando puntos de control en la inicialización del animador

 let controlPoint1 = CGPoint(x: 0.25, y: 0.1) let controlPoint2 = CGPoint(x: 0.25, y: 1) let animator = UIViewPropertyAnimator(duration: 0.3, controlPoint1: controlPoint1, controlPoint2: controlPoint2) 

Este increíble servicio ayudará a elegir puntos de control para sus curvas.

2) UISpringTimingParameters permite a los desarrolladores manipular la relación de amortiguación , la masa , la rigidez y la velocidad inicial para crear el comportamiento de resorte deseado.

 let velocity = CGVector(dx: 1, dy: 0) let springParameters = UISpringTimingParameters(mass: 1.8, stiffness: 330, damping: 33, initialVelocity: velocity) let springAnimator = UIViewPropertyAnimator(duration: 0.0, timingParameters: springParameters) 

El parámetro de duración aún se presenta en Animator, pero se ignorará para el tiempo de spring.

Si estas dos opciones no son suficientes, también puede implementar su propia curva de sincronización confirmando el protocolo UITimingCurveProvider .

Más detalles, cómo crear animaciones con diferentes parámetros de tiempo, puede encontrarlos en la documentación .

También, por favor, vea Avances en la presentación de UIKit Animaciones y Transiciones de la WWDC 2016.

Una forma de crear una función de temporización personalizada es mediante el uso del método functionWithControlPoints :::: en CAMediaTimingFunction (también hay un método initWithControlPoints :::: init correspondiente). Lo que esto hace es crear una curva de Bézier para su función de tiempo. No es una curva arbitraria, pero las curvas de Bézier son muy poderosas y flexibles. Se necesita un poco de práctica para dominar los puntos de control. Un consejo: la mayoría de los progtwigs de dibujo pueden crear curvas de Bézier. Jugar con ellos te dará una retroalimentación visual sobre la curva que estás representando con los puntos de control.

Este enlace apunta a la documentación de Apple. Hay una sección breve pero útil sobre cómo se construyen las funciones de preconstrucción a partir de las curvas.

Editar: El siguiente código muestra una animación de rebote simple. Para hacerlo, creé una función de temporización compuesta ( valores y tiempo de las propiedades de NSArray) y le di a cada segmento de la animación una duración de tiempo diferente (propiedad de clave de tiempo ). De esta manera, puedes componer curvas Bézier para componer tiempos más sofisticados para animaciones. Este es un buen artículo sobre este tipo de animaciones con un buen código de muestra.

 - (void)viewDidLoad { UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, 50.0)]; v.backgroundColor = [UIColor redColor]; CGFloat y = self.view.bounds.size.height; v.center = CGPointMake(self.view.bounds.size.width/2.0, 50.0/2.0); [self.view addSubview:v]; //[CATransaction begin]; CAKeyframeAnimation * animation; animation = [CAKeyframeAnimation animationWithKeyPath:@"position.y"]; animation.duration = 3.0; animation.removedOnCompletion = NO; animation.fillMode = kCAFillModeForwards; NSMutableArray *values = [NSMutableArray array]; NSMutableArray *timings = [NSMutableArray array]; NSMutableArray *keytimes = [NSMutableArray array]; //Start [values addObject:[NSNumber numberWithFloat:25.0]]; [timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)]; [keytimes addObject:[NSNumber numberWithFloat:0.0]]; //Drop down [values addObject:[NSNumber numberWithFloat:y]]; [timings addObject:GetTiming(kCAMediaTimingFunctionEaseOut)]; [keytimes addObject:[NSNumber numberWithFloat:0.6]]; // bounce up [values addObject:[NSNumber numberWithFloat:0.7 * y]]; [timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)]; [keytimes addObject:[NSNumber numberWithFloat:0.8]]; // fihish down [values addObject:[NSNumber numberWithFloat:y]]; [keytimes addObject:[NSNumber numberWithFloat:1.0]]; //[timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)]; animation.values = values; animation.timingFunctions = timings; animation.keyTimes = keytimes; [v.layer addAnimation:animation forKey:nil]; //[CATransaction commit]; } 

No estoy seguro de si todavía está buscando, pero PRTween se ve bastante impresionante en términos de su capacidad para ir más allá de lo que Core Animation le brinda de inmediato , más notablemente, funciones de tiempo personalizadas. También viene empaquetado con muchas, si no todas, las populares curvas de aceleración proporcionadas por varios frameworks web.

Tomé la respuesta de Jesse Crossen y la amplié un poco. Puedes usarlo para animar CGPoints y CGSize por ejemplo. A partir de iOS 7, también puedes usar funciones de tiempo arbitrarias con animaciones UIView.

Puede consultar los resultados en https://github.com/jjackson26/JMJParametricAnimation

Una implementación rápida de la versión es TFAnimation . La demostración es una animación sin curva. TFBasicAnimation como CABasicAnimation excepto que asigne timeFunction a un bloque que no sea timingFunction .

El punto clave es la subclase CAKeyframeAnimation y calcula la posición de los cuadros por timeFunction en timeFunction de 1 / 60fps . Después de todo, añada todo el valor calculado a los values de CAKeyframeAnimation y los tiempos por intervalo a keyTimes también.

Creé un enfoque basado en bloques, que genera un grupo de animación, con múltiples animaciones.

Cada animación, por propiedad, puede usar 1 de 33 curvas paramétricas diferentes, una función de temporización de decaimiento con velocidad inicial o un resorte personalizado configurado según sus necesidades.

Una vez que se genera el grupo, se almacena en caché en la Vista y se puede activar utilizando una AnimationKey, con o sin la animación. Una vez activada, la animación se sincroniza según los valores de la capa de presentación y se aplica en consecuencia.

El marco se puede encontrar aquí FlightAnimator

Aquí hay un ejemplo a continuación:

 struct AnimationKeys { static let StageOneAnimationKey = "StageOneAnimationKey" static let StageTwoAnimationKey = "StageTwoAnimationKey" } ... view.registerAnimation(forKey: AnimationKeys.StageOneAnimationKey, maker: { (maker) in maker.animateBounds(toValue: newBounds, duration: 0.5, easingFunction: .EaseOutCubic) maker.animatePosition(toValue: newPosition, duration: 0.5, easingFunction: .EaseOutCubic) maker.triggerTimedAnimation(forKey: AnimationKeys.StageTwoAnimationKey, onView: self.secondaryView, atProgress: 0.5, maker: { (makerStageTwo) in makerStageTwo.animateBounds(withDuration: 0.5, easingFunction: .EaseOutCubic, toValue: newSecondaryBounds) makerStageTwo.animatePosition(withDuration: 0.5, easingFunction: .EaseOutCubic, toValue: newSecondaryCenter) }) }) 

Para activar la animación

 view.applyAnimation(forKey: AnimationKeys.StageOneAnimationKey)