Flutter - Animation

L'animation est une procédure complexe dans toute application mobile. En dépit de sa complexité, Animation améliore l'expérience utilisateur à un nouveau niveau et offre une interaction utilisateur riche. En raison de sa richesse, l'animation devient partie intégrante de l'application mobile moderne. Le framework Flutter reconnaît l'importance de l'animation et fournit un cadre simple et intuitif pour développer tous les types d'animations.

introduction

L'animation est un processus consistant à montrer une série d'images / d'images dans un ordre particulier pendant une durée spécifique pour donner une illusion de mouvement. Les aspects les plus importants de l'animation sont les suivants -

  • L'animation a deux valeurs distinctes: la valeur de début et la valeur de fin. L'animation commence à partir de la valeur de début et passe par une série de valeurs intermédiaires et se termine enfin aux valeurs de fin. Par exemple, pour animer un widget pour qu'il disparaisse, la valeur initiale sera l'opacité totale et la valeur finale sera l'opacité zéro.

  • Les valeurs intermédiaires peuvent être linéaires ou non linéaires (courbe) par nature et peuvent être configurées. Comprenez que l'animation fonctionne telle qu'elle est configurée. Chaque configuration donne une sensation différente à l'animation. Par exemple, la décoloration d'un widget sera de nature linéaire alors que le rebondissement d'une balle sera de nature non linéaire.

  • La durée du processus d'animation affecte la vitesse (lenteur ou rapidité) de l'animation.

  • La possibilité de contrôler le processus d'animation comme le démarrage de l'animation, l'arrêt de l'animation, la répétition de l'animation pour définir un nombre de fois, l'inversion du processus d'animation, etc.,

  • Dans Flutter, le système d'animation ne fait aucune véritable animation. Au lieu de cela, il fournit uniquement les valeurs requises à chaque image pour rendre les images.

Classes basées sur l'animation

Le système d'animation Flutter est basé sur des objets d'animation. Les classes d'animation de base et son utilisation sont les suivantes -

Animation

Génère des valeurs interpolées entre deux nombres sur une certaine durée. Les classes d'animation les plus courantes sont -

  • Animation<double> - interpoler les valeurs entre deux nombres décimaux

  • Animation<Color> - interpoler les couleurs entre deux couleurs

  • Animation<Size> - interpoler les tailles entre deux tailles

  • AnimationController- Objet spécial Animation pour contrôler l'animation elle-même. Il génère de nouvelles valeurs chaque fois que l'application est prête pour un nouveau cadre. Il prend en charge l'animation linéaire et la valeur commence de 0,0 à 1,0

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

Ici, le contrôleur contrôle l'animation et l'option de durée contrôle la durée du processus d'animation. vsync est une option spéciale utilisée pour optimiser la ressource utilisée dans l'animation.

CourbéAnimation

Similaire à AnimationController mais prend en charge l'animation non linéaire. CurvedAnimation peut être utilisé avec l'objet Animation comme ci-dessous -

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

Tween <T>

Dérivé de Animatable <T> et utilisé pour générer des nombres entre deux nombres autres que 0 et 1. Il peut être utilisé avec l'objet Animation en utilisant la méthode animate et en passant l'objet Animation réel.

AnimationController controller = AnimationController( 
   duration: const Duration(milliseconds: 1000), 
vsync: this); Animation<int> customTween = IntTween(
   begin: 0, end: 255).animate(controller);
  • Tween peut également être utilisé avec CurvedAnimation comme ci-dessous -

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);

Ici, le contrôleur est le véritable contrôleur d'animation. La courbe fournit le type de non-linéarité et le customTween fournit une plage personnalisée de 0 à 255.

Flux de travail de l'animation Flutter

Le flux de travail de l'animation est le suivant -

  • Définissez et démarrez le contrôleur d'animation dans l'initState du StatefulWidget.

AnimationController(duration: const Duration(seconds: 2), vsync: this); 
animation = Tween<double>(begin: 0, end: 300).animate(controller); 
controller.forward();
  • Ajoutez un écouteur basé sur une animation, addListener pour changer l'état du widget.

animation = Tween<double>(begin: 0, end: 300).animate(controller) ..addListener(() {
   setState(() { 
      // The state that has changed here is the animation object’s value. 
   }); 
});
  • Les widgets intégrés, AnimatedWidget et AnimatedBuilder peuvent être utilisés pour ignorer ce processus. Les deux widgets acceptent l'objet Animation et obtiennent les valeurs actuelles requises pour l'animation.

  • Obtenez les valeurs d'animation pendant le processus de construction du widget, puis appliquez-les pour la largeur, la hauteur ou toute propriété pertinente au lieu de la valeur d'origine.

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

Application de travail

Écrivons une application simple basée sur l'animation pour comprendre le concept d'animation dans le cadre Flutter.

  • Créez une nouvelle application Flutter dans le studio Android, product_animation_app.

  • Copiez le dossier des actifs de product_nav_app vers product_animation_app et ajoutez des actifs dans le fichier 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
  • Supprimez le code de démarrage par défaut (main.dart).

  • Ajoutez l'importation et la fonction principale de base.

import 'package:flutter/material.dart'; 
void main() => runApp(MyApp());
  • Créez le widget MyApp dérivé de StatefulWidgtet.

class MyApp extends StatefulWidget { 
   _MyAppState createState() => _MyAppState(); 
}
  • Créez un widget _MyAppState et implémentez initState et supprimez-le en plus de la méthode de construction par défaut.

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();
   }
}

Ici,

  • Dans la méthode initState, nous avons créé un objet contrôleur d'animation (contrôleur), un objet animation (animation) et démarré l'animation à l'aide de controller.forward.

  • Dans la méthode dispose, nous avons supprimé l'objet contrôleur d'animation (contrôleur).

  • Dans la méthode de construction, envoyez une animation au widget MyHomePage via le constructeur. Désormais, le widget MyHomePage peut utiliser l'objet d'animation pour animer son contenu.

  • Maintenant, ajoutez le 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()), 
                           ], 
                        )
                     )
                  )
               ]
            )
         )
      ); 
   }
}
  • Créez un nouveau widget, MyAnimatedWidget pour faire une animation de fondu simple en utilisant l'opacité.

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), 
   ); 
}
  • Ici, nous avons utilisé AniatedBuilder pour faire notre animation. AnimatedBuilder est un widget qui construit son contenu tout en faisant l'animation en même temps. Il accepte un objet d'animation pour obtenir la valeur d'animation actuelle. Nous avons utilisé la valeur d'animation, animation.value pour définir l'opacité du widget enfant. En effet, le widget animera le widget enfant en utilisant le concept d'opacité.

  • Enfin, créez le widget MyHomePage et utilisez l'objet animation pour animer l'un de ses contenus.

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"
               ),
            ],
         )
      );
   }
}

Ici, nous avons utilisé FadeAnimation et MyAnimationWidget pour animer les deux premiers éléments de la liste. FadeAnimation est une classe d'animation intégrée, que nous avons utilisée pour animer son enfant en utilisant le concept d'opacité.

  • Le code complet est le suivant -

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
      ), 
   ); 
}
  • Compilez et exécutez l'application pour voir les résultats. La version initiale et finale de l'application est la suivante -