Animaciones escalonadas

Las animaciones escalonadas son un concepto sencillo: cambios visuales sucede como una serie de operaciones, en lugar de todas a la vez. La animación puede ser puramente secuencial, con un cambio que se produce después del siguiente, o podría superponerse parcial o totalmente. También podría tener intervalos, donde no se producen cambios.

Esta guía muestra cómo construir una animación escalonada en Flutter.

El siguiente video muestra la animación realizada por basic_staggered_animation:

En el video, ves la siguiente animación de un solo widget, que comienza como un cuadrado azul bordeado con esquinas ligeramente redondeadas. El cuadrado sufre cambios en el siguiente orden:

  1. Se desvanece
  2. Ensancha
  3. Se vuelve más alto mientras se mueve hacia arriba
  4. Se transforma en un círculo bordeado.
  5. Cambia de color a naranja

Después de correr hacia adelante, la animación se ejecuta en reversa.

Estructura básica de una animación escalonada.

El siguiente diagrama muestra los intervalos utilizados en el ejemplo basic_staggered_animation. Podrías notar las siguientes características:

  • La opacidad cambia durante el primer 10% de la línea de tiempo.
  • Se produce un pequeño intervalo entre el cambio en la opacidad y el cambio en el ancho.
  • Nada se anima durante el último 25% de la línea de tiempo.
  • Aumentar el padding hace que el widget parezca subir.
  • Al aumentar el radio del borde a 0.5, se transforma el cuadrado con esquinas redondeadas en un círculo.
  • Los cambios de padding y del radio del borde se producen durante el mismo intervalo exacto,   pero no tienen porque hacerlo.

Diagram showing the interval specified for each motion

Para configurar la animación:

  • Cree un AnimationController que administre todas los objetos Animation.
  • Crea un Tween para cada propiedad que se está animando.
    • El Tween define un rango de valores.
    • El método animate de Tween requiere el controlador parent, y produce un objeto Animation para esa propiedad.
  • Especifica el intervalo en la propiedad curve de Animation.

Cuando los valores de los objetos Animation controlados cambian, los nuevos cambios en los valores del Animation, desencadenan la actualización del UI.

El siguiente código crea tween para la propiedad width. Este construye un CurvedAnimation, especificando una “eased curve”. Mira Curves para otras curvas de animación predefinidas disponibles.

width = Tween<double>(
  begin: 50.0,
  end: 150.0,
).animate(
  CurvedAnimation(
    parent: controller,
    curve: Interval(
      0.125, 0.250,
      curve: Curves.ease,
    ),
  ),
),

Los valores begin y end no tienen porque ser double. El siguiente código crea el tween para la propiedad borderRadius (que controla la redondez de las esquinas del cuadrado), usando BorderRadius.circular().

borderRadius = BorderRadiusTween(
  begin: BorderRadius.circular(4.0),
  end: BorderRadius.circular(75.0),
).animate(
  CurvedAnimation(
    parent: controller,
    curve: Interval(
      0.375, 0.500,
      curve: Curves.ease,
    ),
  ),
),

Animación escalonada completa

Como todos los widgets interactivos, la animación completa consiste de un par de widgets: un widget sin estado y uno con estado.

El widget stateless especifica los Tweens, define los objetos Animation y proporciona una función build() responsable de construir la porción de animación del árbol de widgets.

El widget stateful crea el controlador, reproduce la animación, y construye la parte no animada del árbol de widgets. La animación comienza cuando se detecta un toque en cualquier parte de la pantalla.

Código completo para el main.dart de basic_staggered_animation

Stateless widget: StaggerAnimation

En el widget stateless, StaggerAnimation, la función build() crea una instancia de AnimatedBuilder—un Widget de propósito general para la construcción de animaciones. El AnimatedBuilder crea un widget y lo configura usando los valores actuales del Tween. El ejemplo crea una función llamada _buildAnimation () (que realiza las actualizaciones de la UI), y lo asigna a su propiedad builder. AnimatedBuilder escucha las notificaciones del controlador de animación, marcando el árbol de widgets como dirty a medida que cambian los valores. Para cada tick de la animación, los valores se actualizan, dando como resultado una llamada a _buildAnimation ().

clase StaggerAnimation extiende StatelessWidget {
  StaggerAnimation({ Key key, this.controller }) :

    // Cada animación definida aquí transforma su valor durante el subconjunto
    // de la duración del controlador definida por el intervalo de la animación.
    // Por ejemplo, la animación de opacidad transforma su valor durante
    // el primer 10% de la duración del controlador.

    opacity = Tween<double>(
      begin: 0.0,
      end: 1.0,
    ).animate(
      CurvedAnimation(
        parent: controller,
        curve: Interval(
          0.0, 0.100,
          curve: Curves.ease,
        ),
      ),
    ),

    // ... Otras definiciones de tween ...

    super(key: key);

  final Animation<double> controller;
  final Animation<double> opacity;
  final Animation<double> width;
  final Animation<double> height;
  final Animation<EdgeInsets> padding;
  final Animation<BorderRadius> borderRadius;
  final Animation<Color> color;

  // Esta función se llama cada vez que el controlador "ticks" un nuevo marco.
  // Cuando se ejecuta, todos los valores de la animación habrán sido
  // actualizados para reflejar el valor actual del controlador.
  Widget _buildAnimation(BuildContext context, Widget child) {
    return Container(
      padding: padding.value,
      alignment: Alignment.bottomCenter,
      child: Opacity(
        opacity: opacity.value,
        child: Container(
          width: width.value,
          height: height.value,
          decoration: BoxDecoration(
            color: color.value,
            border: Border.all(
              color: Colors.indigo[300],
              width: 3.0,
            ),
            borderRadius: borderRadius.value,
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      builder: _buildAnimation,
      animation: controller,
    );
  }
}

Stateful widget: StaggerDemo

El widget stateful, StaggerDemo, crea el AnimationController (el que los gobierna a todos), especificando una duración de 2000 ms. Ejecuta la animación, y construye la parte no animada del árbol de widgets. La animación comienza cuando se detecta un toque en la pantalla. La animación corre hacia adelante, luego hacia atrás.

class StaggerDemo extends StatefulWidget {
  @override
  _StaggerDemoState createState() => _StaggerDemoState();
}

class _StaggerDemoState extends State<StaggerDemo> with TickerProviderStateMixin {
  AnimationController _controller;

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this
    );
  }

  // ...Boilerplate...

  Future<void> _playAnimation() async {
    try {
      await _controller.forward().orCancel;
      await _controller.reverse().orCancel;
    } on TickerCanceled {
      // La animación se canceló, probablemente porque se ha llamado a dispose.
    }
  }

  @override
  Widget build(BuildContext context) {
    timeDilation = 10.0; // 1.0 is normal animation speed.
    return Scaffold(
      appBar: AppBar(
        title: const Text('Staggered Animation'),
      ),
      body: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: () {
          _playAnimation();
        },
        child: Center(
          child: Container(
            width: 300.0,
            height: 300.0,
            decoration: BoxDecoration(
              color: Colors.black.withOpacity(0.1),
              border: Border.all(
                color:  Colors.black.withOpacity(0.5),
              ),
            ),
            child: StaggerAnimation(
              controller: _controller.view
            ),
          ),
        ),
      ),
    );
  }
}

Recursos

Los siguientes recursos pueden ayudar al escribir animaciones:

Página de inicio de animaciones
Enumera la documentación disponible para animaciones Flutter.   Si los tweens son nuevos para ti, echa un vistazo a la Tutorial de animaciones.
Documentación de la API Flutter
Documentación de referencia para todas las bibliotecas de Flutter.   En particular, ver la documentación de la biblioteca   de animaciones.
Flutter Gallery
Aplicación de demostración que muestra muchos Material Components y otras características de Flutter. La Shrine   manifestación Implementa una animación hero.
Material motion spec
Describe el movimiento para Material apps.