Saltar al contenido principal

Resumen de la API de animaciones

Un resumen de los conceptos de animación.

El sistema de animación en Flutter se basa en objetos tipados Animation. Los widgets pueden incorporar estas animaciones directamente en sus funciones build leyendo su valor actual y escuchando sus cambios de estado o pueden usar las animaciones como base para animaciones más elaboradas que pasan a otros widgets.

Animación

#

El bloque de construcción principal del sistema de animación es la clase Animation. Una animación representa un valor de un tipo específico que puede cambiar a lo largo de la vida útil de la animación. La mayoría de los widgets que realizan una animación reciben un objeto Animation como parámetro, del cual leen el valor actual de la animación y al cual escuchan para detectar cambios en ese valor.

addListener

#

Cada vez que cambia el valor de la animación, la animación notifica a todos los listeners agregados con addListener. Típicamente, un objeto State que escucha una animación llama a setState sobre sí mismo en su callback de listener para notificar al sistema de widgets que necesita reconstruirse con el nuevo valor de la animación.

Este patrón es tan común que existen dos widgets que ayudan a los widgets a reconstruirse cuando las animaciones cambian de valor: AnimatedWidget y AnimatedBuilder. El primero, AnimatedWidget, es más útil para widgets animados stateless. Para usar AnimatedWidget, simplemente crea una subclase de este e implementa la función build. El segundo, AnimatedBuilder, es útil para widgets más complejos que desean incluir una animación como parte de una función build más grande. Para usar AnimatedBuilder, simplemente construye el widget y pásale una función builder.

addStatusListener

#

Las animaciones también proporcionan un AnimationStatus, que indica cómo evolucionará la animación con el tiempo. Cada vez que cambia el estado de la animación, la animación notifica a todos los listeners agregados con addStatusListener. Típicamente, las animaciones comienzan en el estado dismissed, lo que significa que están al principio de su rango. Por ejemplo, las animaciones que avanzan de 0.0 a 1.0 estarán dismissed cuando su valor sea 0.0. Una animación podría ejecutarse hacia adelante (forward, de 0.0 a 1.0) o tal vez en reversa (reverse, de 1.0 a 0.0). Eventualmente, si la animación alcanza el final de su rango (1.0), la animación alcanza el estado completed.

Animation­Controller

#

Para crear una animación, primero crea un AnimationController. Además de ser una animación en sí misma, un AnimationController te permite controlar la animación. Por ejemplo, puedes decirle al controlador que ejecute la animación forward o que detenga (stop) la animación. También puedes lanzar (fling) animaciones, lo que utiliza una simulación física, como un resorte, para impulsar la animación.

Una vez que hayas creado un controlador de animación, puedes comenzar a construir otras animaciones basadas en él. Por ejemplo, puedes crear un ReverseAnimation que refleje la animación original pero se ejecute en la dirección opuesta (de 1.0 a 0.0). De manera similar, puedes crear un CurvedAnimation cuyo valor se ajusta mediante una Curve.

Tweens

#

Para animar más allá del intervalo de 0.0 a 1.0, puedes usar un Tween<T>, que interpola entre sus valores begin y end. Muchos tipos tienen subclases específicas de Tween que proporcionan interpolación específica de tipo. Por ejemplo, ColorTween interpola entre colores y RectTween interpola entre rectángulos (rects). Puedes definir tus propias interpolaciones creando tu propia subclase de Tween y sobrescribiendo su función lerp.

Por sí solo, un tween simplemente define cómo interpolar entre dos valores. Para obtener un valor concreto para el fotograma (frame) actual de una animación, también necesitas una animación para determinar el estado actual. Hay dos formas de combinar un tween con una animación para obtener un valor concreto:

  1. Puedes evaluar (evaluate) el tween en el valor actual de una animación. Este enfoque es muy útil para widgets que ya están escuchando la animación y, por lo tanto, se reconstruyen cada vez que la animación cambia de valor.

  2. Puedes animar (animate) el tween basándote en la animación. En lugar de devolver un solo valor, la función animate devuelve una nueva Animation que incorpora el tween. Este enfoque es muy útil cuando deseas entregar la animación recién creada a otro widget, el cual puede leer el valor actual que incorpora el tween así como escuchar los cambios en dicho valor.

Arquitectura

#

Las animaciones en realidad están construidas a partir de varios bloques de construcción fundamentales.

Scheduler

#

El SchedulerBinding es una clase singleton que expone las primitivas de programación (scheduling) de Flutter.

Para esta discusión, la primitiva clave son las callbacks de fotograma (frame callbacks). Cada vez que un fotograma necesita ser mostrado en la pantalla, el motor de Flutter activa una callback "begin frame" que el scheduler multiplexa a todos los listeners registrados usando scheduleFrameCallback(). A todas estas callbacks se les da la marca de tiempo oficial del fotograma, en forma de un Duration desde alguna época arbitraria. Dado que todas las callbacks tienen el mismo tiempo, cualquier animación activada desde estas callbacks parecerá estar exactamente sincronizada incluso si tardan unos pocos milisegundos en ejecutarse.

Tickers

#

La clase Ticker se engancha al mecanismo scheduleFrameCallback() del scheduler para invocar una callback en cada tick.

Un Ticker se puede iniciar y detener. Cuando se inicia, devuelve un Future que se resolverá cuando se detenga.

En cada tick, el Ticker proporciona a la callback la duración transcurrida desde el primer tick después de que se inició.

Dado que los tickers siempre entregan su tiempo transcurrido en relación con el primer tick después de que se iniciaron; los tickers están todos sincronizados. Si inicias tres tickers en momentos diferentes entre dos ticks, todos ellos estarán no obstante sincronizados con la misma hora de inicio, y posteriormente marcarán al unísono. Como las personas en una parada de autobús, todos los tickers esperan a un evento que ocurre regularmente (el tick) para comenzar a moverse (contar el tiempo).

Simulations

#

La clase abstracta Simulation mapea un valor de tiempo relativo (un tiempo transcurrido) a un valor double, y tiene la noción de finalización.

En principio las simulaciones son stateless, pero en la práctica algunas simulaciones (por ejemplo, BouncingScrollSimulation y ClampingScrollSimulation) cambian de estado de forma irreversible cuando se les consulta.

Existen varias implementaciones concretas de la clase Simulation para diferentes efectos.

Animatables

#

La clase abstracta Animatable mapea un double a un valor de un tipo determinado.

Las clases Animatable son stateless e inmutables.

Tweens

#

La clase abstracta Tween<T> mapea un valor double nominalmente en el rango 0.0-1.0 a un valor tipado (por ejemplo, un Color, u otro double). Es un Animatable.

Tiene la noción de un tipo de salida (T), un valor begin y un valor end de ese tipo, y una forma de interpolar (lerp) entre los valores begin y end para un valor de entrada dado (el double nominalmente en el rango 0.0-1.0).

Las clases Tween son stateless e inmutables.

Composición de animatables

#

Pasar un Animatable<double> (el padre) al método chain() de un Animatable crea una nueva subclase de Animatable que aplica el mapeo del padre y luego el mapeo del hijo.

Curves

#

La clase abstracta Curve mapea doubles nominalmente en el rango 0.0-1.0 a doubles nominalmente en el rango 0.0-1.0.

Las clases Curve son stateless e inmutables.

Animaciones

#

La clase abstracta Animation proporciona un valor de un tipo dado, un concepto de dirección de animación y estado de animación, y una interfaz de listener para registrar callbacks que se invocan cuando cambia el valor o el estado.

Algunas subclases de Animation tienen valores que nunca cambian (kAlwaysCompleteAnimation, kAlwaysDismissedAnimation, AlwaysStoppedAnimation); registrar callbacks en estas no tiene ningún efecto ya que los callbacks nunca son llamados.

La variante Animation<double> es especial porque se puede utilizar para representar un double nominalmente en el rango 0.0-1.0, que es la entrada esperada por las clases Curve y Tween, así como por algunas subclases más de Animation.

Algunas subclases de Animation son stateless, simplemente retransmitiendo listeners a sus padres. Algunas son muy stateful.

Animaciones componibles

#

La mayoría de las subclases de Animation toman un "padre" explícito Animation<double>. Son impulsadas por ese padre.

La subclase CurvedAnimation toma una clase Animation<double> (el padre) y un par de clases Curve (las curvas forward y reverse) como entrada, y usa el valor del padre como entrada para las curvas para determinar su salida. CurvedAnimation es inmutable y stateless.

La subclase ReverseAnimation toma una clase Animation<double> como su padre y revierte todos los valores de la animación. Asume que el padre está usando un valor nominalmente en el rango 0.0-1.0 y devuelve un valor en el rango 1.0-0.0. El estado y la dirección de la animación padre también se revierten. ReverseAnimation es inmutable y stateless.

La subclase ProxyAnimation toma una clase Animation<double> como su padre y simplemente retransmite el estado actual de ese padre. Sin embargo, el padre es mutable.

La subclase TrainHoppingAnimation toma dos padres, y alterna entre ellos cuando sus valores se cruzan.

Controladores de animación

#

El AnimationController es un Animation<double> stateful que utiliza un Ticker para darse vida. Se puede iniciar y detener. En cada tick, toma el tiempo transcurrido desde que se inició y lo pasa a una Simulation para obtener un valor. Ese es entonces el valor que reporta. Si la Simulation reporta que en ese momento ha terminado, entonces el controlador se detiene a sí mismo.

Al controlador de animación se le puede dar un límite inferior y superior para animar entre ellos, y una duración.

En el caso simple (usando forward() o reverse()), el controlador de animación simplemente realiza una interpolación lineal desde el límite inferior al límite superior (o viceversa, para la dirección inversa) durante la duración dada.

Al usar repeat(), el controlador de animación utiliza una interpolación lineal entre los límites dados durante la duración dada, pero no se detiene.

Al usar animateTo(), el controlador de animación realiza una interpolación lineal durante la duración dada desde el valor actual hasta el objetivo dado. Si no se proporciona una duración al método, la duración por defecto del controlador y el rango descrito por los límites inferior y superior del controlador se utilizan para determinar la velocidad de la animación.

Al usar fling(), se utiliza un Force para crear una simulación específica que luego se utiliza para impulsar el controlador.

Al usar animateWith(), la simulación dada se utiliza para impulsar el controlador.

Todos estos métodos devuelven el future que proporciona el Ticker y que se resolverá la próxima vez que el controlador se detenga o cambie de simulación.

Adjuntar animatables a animaciones

#

Pasar un Animation<double> (el nuevo padre) al método animate() de un Animatable crea una nueva subclase de Animation que actúa como el Animatable pero es impulsada desde el padre dado.