Animation d'applications avec Flutter

Publié: 2022-03-10
Résumé rapide ↬ Flutter offre un excellent support d'animation pour les applications multiplateformes. Cet article explore le nouveau moyen non conventionnel et plus simple d'ajouter des animations aux applications. Que sont exactement ces nouveaux widgets « Animation et mouvement » et comment pouvons-nous les utiliser pour ajouter des animations simples et complexes ?

Les applications pour n'importe quelle plate-forme sont louées lorsqu'elles sont intuitives, belles et fournissent des commentaires agréables aux interactions des utilisateurs. L'animation est l'un des moyens d'y parvenir.

Flutter, un framework multiplateforme, a mûri au cours des deux dernières années pour inclure le support Web et de bureau. Il a acquis la réputation que les applications développées avec lui sont fluides et belles. Avec sa prise en charge riche des animations, sa manière déclarative d'écrire l'interface utilisateur, le «rechargement à chaud» et d'autres fonctionnalités, il s'agit désormais d'un cadre multiplateforme complet.

Si vous débutez avec Flutter et que vous souhaitez apprendre une manière non conventionnelle d'ajouter de l'animation, alors vous êtes au bon endroit : nous explorerons le domaine des widgets d'animation et de mouvement, une manière implicite d'ajouter des animations.

Flutter est basé sur le concept de widgets. Chaque composant visuel d'une application est un widget - considérez-les comme des vues dans Android. Flutter fournit un support d'animation à l'aide d'une classe Animation, un objet "AnimationController" pour la gestion et "Tween" pour interpoler la plage de données. Ces trois composants fonctionnent ensemble pour fournir une animation fluide. Étant donné que cela nécessite une création et une gestion manuelles de l'animation, il s'agit d'une manière explicite d'animer.

Permettez-moi maintenant de vous présenter les widgets d'animation et de mouvement. Flutter fournit de nombreux widgets qui prennent en charge l'animation. Il n'est pas nécessaire de créer un objet d'animation ou un contrôleur, car toute l'animation est gérée par cette catégorie de widgets. Choisissez simplement le widget approprié pour l'animation requise et transmettez les valeurs des propriétés du widget à animer. Cette technique est une manière implicite d'animer.

Hiérarchie des animations dans Flutter. ( Grand aperçu )

Le tableau ci-dessus présente à peu près la hiérarchie des animations dans Flutter, comment les animations explicites et implicites sont prises en charge.

Certains des widgets animés couverts dans cet article sont :

  • AnimatedOpacity
  • AnimatedCrossFade
  • AnimatedAlign
  • AnimatedPadding
  • AnimatedSize
  • AnimatedPositioned .

Flutter fournit non seulement des widgets animés prédéfinis, mais également un widget générique appelé AnimatedWidget , qui peut être utilisé pour créer des widgets animés implicitement personnalisés. Comme leur nom l'indique, ces widgets appartiennent à la catégorie des widgets animés et de mouvement, et ils ont donc des propriétés communes qui nous permettent de rendre les animations beaucoup plus fluides et plus belles.

Permettez-moi d'expliquer ces propriétés communes maintenant, car elles seront utilisées plus tard dans tous les exemples.

  • duration
    La durée pendant laquelle animer les paramètres.
  • reverseDuration
    La durée de l'animation inversée.
  • curve
    La courbe à appliquer lors de l'animation des paramètres. Les valeurs interpolées peuvent être tirées d'une distribution linéaire ou, si et quand cela est spécifié, peuvent être tirées d'une courbe.

Commençons le voyage en créant une application simple que nous appellerons "Quoted". Il affichera une citation aléatoire à chaque démarrage de l'application. Deux choses à noter : premièrement, toutes ces citations seront codées en dur dans l'application ; et deuxièmement, aucune donnée utilisateur ne sera enregistrée.

Note : Tous les fichiers de ces exemples se trouvent sur GitHub.

Plus après saut! Continuez à lire ci-dessous ↓

Commencer

Flutter doit être installé et vous aurez besoin de vous familiariser avec le flux de base avant de continuer. Un bon point de départ est "Utiliser Flutter de Google pour un développement mobile véritablement multiplateforme".

Créez un nouveau projet Flutter dans Android Studio.

Nouveau menu de projet Flutter dans Android Studio. ( Grand aperçu )

Cela ouvrira un nouvel assistant de projet, où vous pourrez configurer les bases du projet.

Écran de sélection du type de projet Flutter. ( Grand aperçu )

Dans l'écran de sélection du type de projet, il existe différents types de projets Flutter, chacun correspondant à un scénario spécifique. Pour ce didacticiel, choisissez Application Flutter et appuyez sur Suivant .

Vous devez maintenant entrer des informations spécifiques au projet : le nom et le chemin du projet, le domaine de l'entreprise, etc. Jetez un oeil à l'image ci-dessous.

Écran de configuration de l'application Flutter. ( Grand aperçu )

Ajoutez le nom du projet, le chemin du SDK Flutter, l'emplacement du projet et une description facultative du projet. Appuyez sur Suivant .

Écran de nom du package d'application Flutter. ( Grand aperçu )

Chaque application (que ce soit Android ou iOS) nécessite un nom de package unique. Généralement, vous utilisez l'inverse du domaine de votre site Web ; par exemple, com.google ou com.yahoo. Appuyez sur Terminer pour générer une application Flutter fonctionnelle.

Exemple de projet généré. ( Grand aperçu )

Une fois le projet généré, vous devriez voir l'écran ci-dessus. Ouvrez le fichier main.dart (mis en surbrillance dans la capture d'écran). Il s'agit du dossier principal de l'application. L'exemple de projet est complet en lui-même et peut être exécuté directement sur un émulateur ou un périphérique physique sans aucune modification.

Remplacez le contenu du fichier main.dart par l'extrait de code suivant :

 import 'package:animated_widgets/FirstPage.dart'; import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Animated Widgets', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, accentColor: Colors.redAccent, ), home: FirstPage(), ); } }

Ce code nettoie le fichier main.dart en ajoutant simplement des informations simples pertinentes à la création d'une nouvelle application. La classe MyApp renvoie un objet : un widget MaterialApp , qui fournit la structure de base pour créer des applications conformes à Material Design. Pour rendre le code plus structuré, créez deux nouveaux fichiers dart dans le dossier lib : FirstPage.dart et Quotes.dart .

Le fichier FirstPage.dart. ( Grand aperçu )

FirstPage.dart contiendra tout le code responsable de tous les éléments visuels (widgets) requis pour notre application Quoted. Toute l'animation est gérée dans ce fichier.

Remarque : plus loin dans l'article, tous les extraits de code de chaque widget animé sont ajoutés à ce fichier en tant qu'enfants du widget Scaffold. Pour plus d'informations, cet exemple sur GitHub pourrait être utile.

Commencez par ajouter le code suivant à FirstPage.dart . C'est le code partiel où d'autres choses seront ajoutées plus tard.

 import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } } import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } } import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } } import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
Le fichier Quotes.dart. ( Grand aperçu )

Le fichier Quotes.dart contient une liste de toutes les citations codées en dur. Un point à noter ici est que la liste est un objet statique. Cela signifie qu'il peut être utilisé à d'autres endroits sans créer un nouvel objet de la classe Quotes. Ceci est choisi par conception, car la liste ci-dessus agit simplement comme un utilitaire.

Ajoutez le code suivant à ce fichier :

 class Quotes { static const quotesArray = [ "Good, better, best. Never let it rest. 'Til your good is better and your better is best", "It does not matter how slowly you go as long as you do not stop.", "Only I can change my life. No one can do it for me." ]; }

Le squelette du projet est maintenant prêt, alors étoffons Citation un peu plus.

AnimatedOpacity

Pour donner une touche personnelle à l'application, ce serait bien de connaître le nom de l'utilisateur, alors demandons-le et montrons un bouton suivant. Jusqu'à ce que l'utilisateur entre son nom, ce bouton est masqué et il s'affichera gracieusement lorsqu'un nom est donné. Nous avons besoin d'une sorte d'animation de visibilité pour le bouton, mais existe-t-il un widget pour cela ? Oui il y a.

Entrez AnimatedOpacity . Ce widget s'appuie sur le widget Opacité en ajoutant un support d'animation implicite. Comment l'utilisons-nous ? Rappelez-vous notre scénario : nous devons afficher un bouton suivant avec une visibilité animée. Nous enveloppons le widget de bouton dans le widget AnimatedOpacity , introduisons certaines valeurs appropriées et ajoutons une condition pour déclencher l'animation - et Flutter peut gérer le reste.

 _getAnimatedOpacityButton() { return AnimatedOpacity( duration: Duration(seconds: 1), reverseDuration: Duration(seconds: 1), curve: Curves.easeInOut, opacity: showNextButton ? 1 : 0, child: _getButton(), ); }
Animation d'opacité du bouton suivant. ( Grand aperçu )

Le widget AnimatedOpacity a deux propriétés obligatoires :

  • opacity
    Une valeur de 1 signifie complètement visible ; 0 (zéro) signifie caché. Lors de l'animation, Flutter interpole les valeurs entre ces deux extrêmes. Vous pouvez voir comment une condition est placée pour modifier la visibilité, déclenchant ainsi l'animation.
  • child
    Le widget enfant dont la visibilité sera animée.

Vous devez maintenant comprendre à quel point il est simple d'ajouter une animation de visibilité avec le widget implicite. Et tous ces widgets suivent les mêmes directives et sont faciles à utiliser. Passons au suivant.

AnimatedCrossFade

Nous avons le nom de l'utilisateur, mais le widget attend toujours une entrée. Dans l'étape précédente, lorsque l'utilisateur saisit son nom, nous affichons le bouton suivant. Maintenant, lorsque l'utilisateur appuie sur le bouton, je veux arrêter d'accepter les entrées et afficher le nom saisi. Il existe de nombreuses façons de le faire, bien sûr, mais nous pouvons peut-être masquer le widget d'entrée et afficher un widget de texte non modifiable. Essayons-le en utilisant le widget AnimatedCrossFade .

Ce widget nécessite deux enfants, car le widget effectue un fondu enchaîné entre eux en fonction de certaines conditions. Une chose importante à garder à l'esprit lors de l'utilisation de ce widget est que les deux enfants doivent avoir la même largeur. Si la hauteur est différente, le widget le plus grand est coupé par le bas. Dans ce scénario, deux widgets seront utilisés comme enfants : input et label.

 _getAnimatedCrossfade() { return AnimatedCrossFade( duration: Duration(seconds: 1), alignment: Alignment.center, reverseDuration: Duration(seconds: 1), firstChild: _getNameInputWidget(), firstCurve: Curves.easeInOut, secondChild: _getNameLabelWidget(), secondCurve: Curves.easeInOut, crossFadeState: showNameLabel ? CrossFadeState.showSecond : CrossFadeState.showFirst, ); }
Fondu enchaîné entre le widget d'entrée et le widget de nom. ( Grand aperçu )

Ce widget nécessite un ensemble différent de paramètres obligatoires :

  • crossFadeState
    Cet état détermine quel enfant montrer.
  • firstChild
    Spécifie le premier enfant de ce widget.
  • secondChild
    Spécifie le deuxième enfant.

AnimatedAlign

À ce stade, l'étiquette de nom est positionnée au centre de l'écran. Cela aura l'air beaucoup mieux en haut, car nous avons besoin du centre de l'écran pour afficher les citations. En termes simples, l'alignement du widget d'étiquette de nom doit être modifié du centre vers le haut. Et ne serait-il pas agréable d'animer ce changement d'alignement avec l'animation de fondu enchaîné précédente ? Faisons-le.

Comme toujours, plusieurs techniques peuvent être utilisées pour y parvenir. Étant donné que le widget d'étiquette de nom est déjà aligné au centre, animer son alignement serait beaucoup plus simple que de manipuler les valeurs supérieures et gauches du widget. Le widget AnimatedAlign est parfait pour ce travail.

Pour lancer cette animation, un déclencheur est requis. Le seul but de ce widget est d'animer le changement d'alignement, il n'a donc que quelques propriétés : ajouter un enfant, définir son alignement, déclencher le changement d'alignement, et c'est tout.

 _getAnimatedAlignWidget() { return AnimatedAlign( duration: Duration(seconds: 1), curve: Curves.easeInOut, alignment: alignTop ? Alignment.topLeft : Alignment.center, child: _getAnimatedCrossfade(), ); }
Animation d'alignement du widget de nom. ( Grand aperçu )

Il n'a que deux propriétés obligatoires :

  • enfant:
    L'enfant dont l'alignement sera modifié.
  • alignement:
    Valeur d'alignement requise.

Ce widget est vraiment simple mais les résultats sont élégants. De plus, nous avons vu avec quelle facilité nous pouvons utiliser deux widgets animés différents pour créer une animation plus complexe. C'est la beauté des widgets animés.

AnimatedPadding

Nous avons maintenant le nom de l'utilisateur en haut, animé en douceur sans trop d'effort, en utilisant différents types de widgets animés. Ajoutons une salutation, "Salut", avant le nom. L'ajout d'un widget de texte avec la valeur "Salut" en haut le fera chevaucher le widget de texte d'accueil, ressemblant à l'image ci-dessous.

Les widgets de salutation et de nom se chevauchent. ( Grand aperçu )

Et si le widget de texte du nom avait un rembourrage à gauche ? Augmenter le rembourrage gauche fonctionnera certainement, mais attendez : pouvons-nous augmenter le rembourrage avec une animation ? Oui, et c'est ce que fait AnimatedPadding . Pour rendre tout cela beaucoup plus beau, faisons en sorte que le widget de texte de salutation apparaisse en fondu et que le rembourrage du widget de texte de nom augmente en même temps.

 _getAnimatedPaddingWidget() { return AnimatedPadding( duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn, padding: increaseLeftPadding ? EdgeInsets.only(left: 28.0) : EdgeInsets.only(left: 0), child: _getAnimatedCrossfade(), ); }

Étant donné que l'animation ci-dessus ne devrait se produire qu'une fois l'alignement animé précédent terminé, nous devons retarder le déclenchement de cette animation. S'éloignant brièvement du sujet, c'est le bon moment pour parler d'un mécanisme populaire pour ajouter du retard. Flutter fournit plusieurs de ces techniques, mais le constructeur Future.delayed est l'une des approches les plus simples, les plus propres et les plus lisibles. Par exemple, pour exécuter un morceau de code après 1 seconde :

 Future.delayed(Duration(seconds: 1), (){ sum = a + b; // This sum will be calculated after 1 second. print(sum); });

Comme la durée du retard est déjà connue (calculée à partir des durées d'animation précédentes), l'animation peut être déclenchée après cet intervalle.

 // Showing “Hi” after 1 second - greetings visibility trigger. _showGreetings() { Future.delayed(Duration(seconds: 1), () { setState(() { showGreetings = true; }); }); } // Increasing the padding for name label widget after 1 second - increase padding trigger. _increaseLeftPadding() { Future.delayed(Duration(seconds: 1), () { setState(() { increaseLeftPadding = true; }); }); }
Animation de rembourrage du widget de nom. ( Grand aperçu )

Ce widget n'a que deux propriétés obligatoires :

  • child
    L'enfant à l'intérieur de ce widget, auquel le rembourrage sera appliqué.
  • padding
    La quantité d'espace à ajouter.

AnimatedSize

Aujourd'hui, toute application ayant une sorte d'animation inclura un zoom avant ou arrière sur les composants visuels pour attirer l'attention de l'utilisateur (communément appelée animation de mise à l'échelle). Pourquoi ne pas utiliser la même technique ici ? Nous pouvons montrer à l'utilisateur une citation de motivation qui zoome depuis le centre de l'écran. Permettez-moi de vous présenter le widget AnimatedSize , qui active les effets de zoom avant et arrière, contrôlés en modifiant la taille de son enfant.

Ce widget est un peu différent des autres en ce qui concerne les paramètres requis. Nous avons besoin de ce que Flutter appelle un "Ticker". Flutter a une méthode pour informer les objets chaque fois qu'un nouvel événement de cadre est déclenché. Cela peut être considéré comme quelque chose qui envoie un signal disant : « Faites-le maintenant ! … Fais le maintenant! … Fais le maintenant! …”

Le widget AnimatedSize nécessite une propriété - vsync - qui accepte un fournisseur de ticker. Le moyen le plus simple d'obtenir un fournisseur de tickers est d'ajouter un Mixin à la classe. Il existe deux implémentations de base du fournisseur de tickers : SingleTickerProviderStateMixin , qui fournit un seul ticker ; et TickerProviderStateMixin , qui en fournit plusieurs.

L'implémentation par défaut d'un Ticker est utilisée pour marquer les images d'une animation. Dans ce cas, ce dernier est utilisé. En savoir plus sur les mixins.

 // Helper method to create quotes card widget. _getQuoteCardWidget() { return Card( color: Colors.green, elevation: 8.0, child: _getAnimatedSizeWidget(), ); } // Helper method to create animated size widget and set its properties. _getAnimatedSizeWidget() { return AnimatedSize( duration: Duration(seconds: 1), curve: Curves.easeInOut, vsync: this, child: _getQuoteContainer(), ); } // Helper method to create the quotes container widget with different sizes. _getQuoteContainer() { return Container( height: showQuoteCard ? 100 : 0, width: showQuoteCard ? screenWidth - 32 : 0, child: Center( child: Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Text(quote, style: TextStyle(color: Colors.white, fontWeight: FontWeight.w400, fontSize: 14),), ), ), ); } // Trigger used to show the quote card widget. _showQuote() { Future.delayed(Duration(seconds: 2), () { setState(() { showQuoteCard = true; }); }); }
Animation de mise à l'échelle du widget de cotations. ( Grand aperçu )

Propriétés obligatoires pour ce widget :

  • vsync
    Le fournisseur de ticker requis pour coordonner l'animation et les changements d'image.<
  • child
    L'enfant dont la taille change sera animé.

L'animation de zoom avant et arrière est maintenant facilement apprivoisée.

AnimatedPositioned

Génial! Les citations zooment depuis le centre pour attirer l'attention de l'utilisateur. Et s'il glissait du bas pendant le zoom avant ? Essayons. Ce mouvement consiste à jouer avec la position du widget de devis et à animer les changements de propriétés de position. AnimatedPositioned est le candidat idéal.

Ce widget change automatiquement la position de l'enfant sur une durée donnée chaque fois que la position spécifiée change. Un point à noter : cela ne fonctionne que si son widget parent est une "pile". Ce widget est assez simple et facile à utiliser. Voyons voir.

 // Helper method to create the animated positioned widget. // With position changes based on “showQuoteCard” flag. _getAnimatedPositionWidget() { return AnimatedPositioned( duration: Duration(seconds: 1), curve: Curves.easeInOut, child: _getQuoteCardWidget(), top: showQuoteCard ? screenHeight/2 - 100 : screenHeight, left: !showQuoteCard ? screenWidth/2 : 12, ); }
Position avec animation de mise à l'échelle des guillemets. ( Grand aperçu )

Ce widget n'a qu'une seule propriété obligatoire :

  • child
    Le widget dont la position sera modifiée.

Si la taille de l'enfant ne devrait pas changer avec sa position, une alternative plus performante à ce widget serait SlideTransition .

Voici notre animation complète :

Tous les widgets animés ensemble. ( Grand aperçu )

Conclusion

Les animations font partie intégrante de l'expérience utilisateur. Les applications statiques ou les applications avec une animation janky réduisent non seulement la rétention des utilisateurs, mais également la réputation d'un développeur pour fournir des résultats.

Aujourd'hui, les applications les plus populaires ont une sorte d'animation subtile pour ravir les utilisateurs. Les commentaires animés aux demandes des utilisateurs peuvent également les inciter à explorer davantage. Flutter offre de nombreuses fonctionnalités pour le développement multiplateforme, y compris un support riche pour des animations fluides et réactives.

Flutter a un excellent support de plug-in qui nous permet d'utiliser des animations d'autres développeurs. Maintenant qu'il a mûri jusqu'à la version 1.9, avec tant d'amour de la part de la communauté, Flutter est appelé à s'améliorer à l'avenir. Je dirais que c'est le moment idéal pour apprendre Flutter !

Autres ressources

  • "Widgets d'animation et de mouvement", Flutter Docs
  • "Introduction aux animations", Flutter Docs
  • Flutter Codelabs

Note de l'éditeur : Un immense merci à Ahmad Awais pour son aide dans la révision de cet article.