Flutter - Animación

La animación es un procedimiento complejo en cualquier aplicación móvil. A pesar de su complejidad, la animación mejora la experiencia del usuario a un nuevo nivel y proporciona una rica interacción con el usuario. Debido a su riqueza, la animación se convierte en una parte integral de las aplicaciones móviles modernas. El marco Flutter reconoce la importancia de la animación y proporciona un marco simple e intuitivo para desarrollar todo tipo de animaciones.

Introducción

La animación es un proceso de mostrar una serie de imágenes / imágenes en un orden particular dentro de una duración específica para dar una ilusión de movimiento. Los aspectos más importantes de la animación son los siguientes:

  • La animación tiene dos valores distintos: valor inicial y valor final. La animación comienza desde el valor de Inicio y pasa por una serie de valores intermedios y finalmente termina en los valores de Fin. Por ejemplo, para animar un widget para que desaparezca, el valor inicial será la opacidad total y el valor final será la opacidad cero.

  • Los valores intermedios pueden ser de naturaleza lineal o no lineal (curva) y se puede configurar. Comprenda que la animación funciona como está configurada. Cada configuración proporciona una sensación diferente a la animación. Por ejemplo, el desvanecimiento de un widget será de naturaleza lineal, mientras que el rebote de una pelota será de naturaleza no lineal.

  • La duración del proceso de animación afecta la velocidad (lentitud o rapidez) de la animación.

  • La capacidad de controlar el proceso de animación como iniciar la animación, detener la animación, repetir la animación para establecer un número de veces, invertir el proceso de animación, etc.

  • En Flutter, el sistema de animación no realiza ninguna animación real. En cambio, proporciona solo los valores necesarios en cada fotograma para representar las imágenes.

Clases basadas en animación

El sistema de animación Flutter se basa en objetos de animación. Las clases principales de animación y su uso son las siguientes:

Animación

Genera valores interpolados entre dos números durante un período determinado. Las clases de animación más comunes son:

  • Animation<double> - interpolar valores entre dos números decimales

  • Animation<Color> - interpolar colores entre dos colores

  • Animation<Size> - interpolar tamaños entre dos tamaños

  • AnimationController- Objeto de animación especial para controlar la propia animación. Genera nuevos valores siempre que la aplicación esté lista para un nuevo marco. Admite animación basada en lineales y el valor comienza desde 0.0 a 1.0

controller = AnimationController(duration: const Duration(seconds: 2), vsync: this);

Aquí, el controlador controla la animación y la opción de duración controla la duración del proceso de animación. vsync es una opción especial que se utiliza para optimizar el recurso utilizado en la animación.

Animación Curvada

Similar a AnimationController pero admite animación no lineal. CurvedAnimation se puede usar junto con el objeto Animation como se muestra a continuación:

controller = AnimationController(duration: const Duration(seconds: 2), vsync: this); 
animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)

Tween <T>

Derivado de Animatable <T> y utilizado para generar números entre dos números que no sean 0 y 1. Se puede utilizar junto con el objeto Animation utilizando el método animado y pasando el objeto Animation real.

AnimationController controller = AnimationController( 
   duration: const Duration(milliseconds: 1000), 
vsync: this); Animation<int> customTween = IntTween(
   begin: 0, end: 255).animate(controller);
  • Tween también se puede usar junto con CurvedAnimation como se muestra a continuación:

AnimationController controller = AnimationController(
   duration: const Duration(milliseconds: 500), vsync: this); 
final Animation curve = CurvedAnimation(parent: controller, curve: Curves.easeOut); 
Animation<int> customTween = IntTween(begin: 0, end: 255).animate(curve);

Aquí, el controlador es el controlador de animación real. La curva proporciona el tipo de no linealidad y customTween proporciona un rango personalizado de 0 a 255.

Flujo de trabajo de la animación Flutter

El flujo de trabajo de la animación es el siguiente:

  • Defina e inicie el controlador de animación en initState del StatefulWidget.

AnimationController(duration: const Duration(seconds: 2), vsync: this); 
animation = Tween<double>(begin: 0, end: 300).animate(controller); 
controller.forward();
  • Agregue un oyente basado en animación, addListener para cambiar el estado del widget.

animation = Tween<double>(begin: 0, end: 300).animate(controller) ..addListener(() {
   setState(() { 
      // The state that has changed here is the animation object’s value. 
   }); 
});
  • Los widgets integrados, AnimatedWidget y AnimatedBuilder se pueden utilizar para omitir este proceso. Ambos widgets aceptan objetos de animación y obtienen los valores actuales necesarios para la animación.

  • Obtenga los valores de la animación durante el proceso de construcción del widget y luego aplíquelos para el ancho, alto o cualquier propiedad relevante en lugar del valor original.

child: Container( 
   height: animation.value, 
   width: animation.value, 
   child: <Widget>, 
)

Aplicación de trabajo

Escribamos una aplicación sencilla basada en animación para comprender el concepto de animación en el marco de Flutter.

  • Cree una nueva aplicación Flutter en Android Studio, product_animation_app.

  • Copie la carpeta de activos de product_nav_app a product_animation_app y agregue activos dentro del archivo pubspec.yaml.

flutter: 
   assets: 
   - assets/appimages/floppy.png 
   - assets/appimages/iphone.png 
   - assets/appimages/laptop.png 
   - assets/appimages/pendrive.png 
   - assets/appimages/pixel.png 
   - assets/appimages/tablet.png
  • Elimina el código de inicio predeterminado (main.dart).

  • Agregue importación y función principal básica.

import 'package:flutter/material.dart'; 
void main() => runApp(MyApp());
  • Cree el widget MyApp derivado de StatefulWidgtet.

class MyApp extends StatefulWidget { 
   _MyAppState createState() => _MyAppState(); 
}
  • Cree el widget _MyAppState e implemente initState y elimínelo además del método de compilación predeterminado.

class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin { 
   Animation<double> animation; 
   AnimationController controller; 
   @override void initState() {
      super.initState(); 
      controller = AnimationController(
         duration: const Duration(seconds: 10), vsync: this
      ); 
      animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller); 
      controller.forward(); 
   } 
   // This widget is the root of your application. 
   @override 
   Widget build(BuildContext context) {
      controller.forward(); 
      return MaterialApp(
         title: 'Flutter Demo',
         theme: ThemeData(primarySwatch: Colors.blue,), 
         home: MyHomePage(title: 'Product layout demo home page', animation: animation,)
      ); 
   } 
   @override 
   void dispose() {
      controller.dispose();
      super.dispose();
   }
}

Aquí,

  • En el método initState, hemos creado un objeto controlador de animación (controlador), un objeto de animación (animación) y comenzamos la animación usando controller.forward.

  • En el método de eliminación, hemos eliminado el objeto controlador de animación (controlador).

  • En el método de compilación, envíe la animación al widget MyHomePage a través del constructor. Ahora, el widget MyHomePage puede usar el objeto de animación para animar su contenido.

  • Ahora, agregue el widget ProductBox

class ProductBox extends StatelessWidget {
   ProductBox({Key key, this.name, this.description, this.price, this.image})
      : super(key: key);
   final String name; 
   final String description; 
   final int price; 
   final String image; 
   
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), 
         height: 140, 
         child: Card( 
            child: Row( 
               mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
               children: <Widget>[ 
                  Image.asset("assets/appimages/" + image), 
                  Expanded( 
                     child: Container( 
                        padding: EdgeInsets.all(5), 
                        child: Column( 
                           mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
                           children: <Widget>[ 
                              Text(this.name, style: 
                                 TextStyle(fontWeight: FontWeight.bold)), 
                              Text(this.description), 
                                 Text("Price: " + this.price.toString()), 
                           ], 
                        )
                     )
                  )
               ]
            )
         )
      ); 
   }
}
  • Cree un nuevo widget, MyAnimatedWidget para hacer una animación de fundido simple usando opacidad.

class MyAnimatedWidget extends StatelessWidget { 
   MyAnimatedWidget({this.child, this.animation}); 
      
   final Widget child; 
   final Animation<double> animation; 
   
   Widget build(BuildContext context) => Center( 
   child: AnimatedBuilder(
      animation: animation, 
      builder: (context, child) => Container( 
         child: Opacity(opacity: animation.value, child: child), 
      ), 
      child: child), 
   ); 
}
  • Aquí, hemos utilizado AniateBuilder para hacer nuestra animación. AnimatedBuilder es un widget que construye su contenido mientras hace la animación al mismo tiempo. Acepta un objeto de animación para obtener el valor de animación actual. Hemos utilizado el valor de animación, animation.value para establecer la opacidad del widget secundario. En efecto, el widget animará el widget secundario usando el concepto de opacidad.

  • Finalmente, cree el widget MyHomePage y use el objeto de animación para animar cualquiera de su contenido.

class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title, this.animation}) : super(key: key); 
   
   final String title; 
   final Animation<double> 
   animation; 
   
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text("Product Listing")),body: ListView(
            shrinkWrap: true,
            padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0), 
            children: <Widget>[
               FadeTransition(
                  child: ProductBox(
                     name: "iPhone", 
                     description: "iPhone is the stylist phone ever", 
                     price: 1000, 
                     image: "iphone.png"
                  ), opacity: animation
               ), 
               MyAnimatedWidget(child: ProductBox(
                  name: "Pixel", 
                  description: "Pixel is the most featureful phone ever", 
                  price: 800, 
                  image: "pixel.png"
               ), animation: animation), 
               ProductBox(
                  name: "Laptop", 
                  description: "Laptop is most productive development tool", 
                  price: 2000, 
                  image: "laptop.png"
               ), 
               ProductBox(
                  name: "Tablet", 
                  description: "Tablet is the most useful device ever for meeting", 
                  price: 1500, 
                  image: "tablet.png"
               ), 
               ProductBox(
                  name: "Pendrive", 
                  description: "Pendrive is useful storage medium", 
                  price: 100, 
                  image: "pendrive.png"
               ),
               ProductBox(
                  name: "Floppy Drive", 
                  description: "Floppy drive is useful rescue storage medium", 
                  price: 20, 
                  image: "floppy.png"
               ),
            ],
         )
      );
   }
}

Aquí, hemos utilizado FadeAnimation y MyAnimationWidget para animar los dos primeros elementos de la lista. FadeAnimation es una clase de animación incorporada, que usamos para animar a su hijo usando el concepto de opacidad.

  • El código completo es el siguiente:

import 'package:flutter/material.dart'; 
void main() => runApp(MyApp()); 

class MyApp extends StatefulWidget { 
   _MyAppState createState() => _MyAppState(); 
} 
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
   Animation<double> animation; 
   AnimationController controller; 
   
   @override 
   void initState() {
      super.initState(); 
      controller = AnimationController(
         duration: const Duration(seconds: 10), vsync: this); 
      animation = Tween<double>(begin: 0.0, end: 1.0).animate(controller); 
      controller.forward(); 
   } 
   // This widget is the root of your application. 
   @override 
   Widget build(BuildContext context) {
      controller.forward(); 
      return MaterialApp( 
         title: 'Flutter Demo', theme: ThemeData(primarySwatch: Colors.blue,), 
         home: MyHomePage(title: 'Product layout demo home page', animation: animation,) 
      ); 
   } 
   @override 
   void dispose() {
      controller.dispose();
      super.dispose(); 
   } 
}
class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title, this.animation}): super(key: key);
   final String title; 
   final Animation<double> animation; 
   
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(title: Text("Product Listing")), 
         body: ListView(
            shrinkWrap: true, 
            padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0), 
            children: <Widget>[
               FadeTransition(
                  child: ProductBox(
                     name: "iPhone", 
                     description: "iPhone is the stylist phone ever", 
                     price: 1000, 
                     image: "iphone.png"
                  ), 
                  opacity: animation
               ), 
               MyAnimatedWidget(
                  child: ProductBox( 
                     name: "Pixel", 
                     description: "Pixel is the most featureful phone ever", 
                     price: 800, 
                     image: "pixel.png"
                  ), 
                  animation: animation
               ), 
               ProductBox( 
                  name: "Laptop", 
                  description: "Laptop is most productive development tool", 
                  price: 2000, 
                  image: "laptop.png"
               ), 
               ProductBox(
                  name: "Tablet",
                  description: "Tablet is the most useful device ever for meeting",
                  price: 1500, 
                  image: "tablet.png"
               ), 
               ProductBox(
                  name: "Pendrive", 
                  description: "Pendrive is useful storage medium", 
                  price: 100, 
                  image: "pendrive.png"
               ), 
               ProductBox(
                  name: "Floppy Drive", 
                  description: "Floppy drive is useful rescue storage medium", 
                  price: 20, 
                  image: "floppy.png"
               ), 
            ], 
         )
      ); 
   } 
} 
class ProductBox extends StatelessWidget { 
   ProductBox({Key key, this.name, this.description, this.price, this.image}) :
      super(key: key);
   final String name; 
   final String description; 
   final int price; 
   final String image; 
   Widget build(BuildContext context) {
      return Container(
         padding: EdgeInsets.all(2), 
         height: 140, 
         child: Card(
            child: Row(
               mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
               children: <Widget>[ 
                  Image.asset("assets/appimages/" + image), 
                  Expanded(
                     child: Container( 
                        padding: EdgeInsets.all(5), 
                        child: Column( 
                           mainAxisAlignment: MainAxisAlignment.spaceEvenly, 
                           children: <Widget>[ 
                              Text(
                                 this.name, style: TextStyle(
                                    fontWeight: FontWeight.bold
                                 )
                              ), 
                              Text(this.description), Text(
                                 "Price: " + this.price.toString()
                              ), 
                           ], 
                        )
                     )
                  ) 
               ]
            )
         )
      ); 
   } 
}
class MyAnimatedWidget extends StatelessWidget { 
   MyAnimatedWidget({this.child, this.animation}); 
   final Widget child; 
   final Animation<double> animation; 
 
   Widget build(BuildContext context) => Center( 
      child: AnimatedBuilder(
         animation: animation, 
         builder: (context, child) => Container( 
            child: Opacity(opacity: animation.value, child: child), 
         ), 
         child: child
      ), 
   ); 
}
  • Compile y ejecute la aplicación para ver los resultados. La versión inicial y final de la aplicación es la siguiente: