Animations explicites

Animations explicites avec « built-in » : les FooTransition

RappelQu'est-ce qu'un controller ?

Un controller nous permet de suivre l'état d'un widget et de, comme son nom l'indique, le controller. Par exemple, il est possible grâce à ScrollController de comprendre lorsque nous avons atteint le bout d'une liste, et de lui demander de remonter au début. Ici, nous utiliserons principalement le AnimationController, qui n'aura bientôt plus de secrets pour vous !

Info utile : la valeur d'un controller varie entre 0 et 1, et dans le cas d'un AnimationController, 0 correspond au début de l'animation et 1 au moment où l'animation est terminée. Donc si le paramètre duration est de 3 secondes, le controller aura 0 de valeur à la seconde 0, et 1 de valeur à la seconde 3.

Comme nous l'avons vu plus tôt dans ce cours, il est d'ores et déjà possible de créer des animations qui vont et viennent entre leurs valeurs de départ et leur valeur finale. Mais comment faisons-nous si nous souhaitons créer des animations qui se répètent depuis le début, encore et encore ? Les animations explicites « built-in » sont là pour vous !

Fondamental

Les animations explicites avec « built-in » (ou FooTransition) sont des extensions des AnimatedWidget que nous étudierons par la suite.

Les FooTransition comportent notamment :

  • Sizetransition

  • FadeTransition

  • ScaleTransition

  • SlideTransition

  • RotationTransition

  • PositionedTransition

  • DecoratedBoxTransition

  • DefaultTextStyleTransition

  • RelativePosititonedTransition

Pour l'exemple, nous allons ici utiliser le SizeTransition pour donner l'effet d'une balle rebondissante à notre Container.

1
class MyStatefulWidget extends StatefulWidget { @override
2
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
3
}
4
5
class _MyStatefulWidgetState extends State<MyStatefulWidget> with SingleTickerProviderStateMixin {
6
AnimationController _controller; Animation _animation;
7
8
@override
9
void initState() { super.initState();
10
_controller = AnimationController( duration: const Duration(seconds: 3), vsync: this,
11
)..repeat();
12
_animation = CurvedAnimation( parent: _controller,
13
curve: Curves.bounceInOut,
14
);
15
}
16
17
@override void dispose() {
18
super.dispose();
19
_controller.dispose();
20
}
21
22
@override
23
Widget build(BuildContext context) {
24
... }
25
}

L'utilisation d'un controller implique ici l'utilisation d'un SingleTickerProviderStateMixin. Il est ensuite nécessaire d'initialiser notre AnimationController dans initState((){}), en lui indiquant notamment le paramètre duration de notre animation, ainsi que vsync. vsync s'occupe de garder le suivi de l'écran, afin de ne pas continuer à générer l'animation lorsque celle-ci n'apparaît pas.

Nous initialisons ensuite notre animation en lui donnant comme parent le controller que nous venons de créer. Ainsi, nous n'avons plus qu'à dire au controller ce que nous souhaitons, et il l'appliquera à l'animation ! Ici, nous lui demandons de se répéter à l'infini grâce à ..repeat(). AnimationController propose ainsi toutes sortes d'actions que nous pouvons appliquer à nos animations, comme stop() pour arrêter l'animation ou forward() pour ne jouer l'animation qu'une fois.

Attention, il ne faut pas oublier de disposer le controller dans dispose() pour éviter les fuites mémoire !

Maintenant, passons à la création du widget à qui s'appliquera l'animation. Pour ça, rien de plus simple : il faut créer notre widget SizeTransition, lui donner en sizeFactor notre animation (qui dépend donc déjà de notre controller) et mettre le child (ici Center).

1
Widget build(BuildContext context) { return Scaffold(
2
body: SizeTransition( sizeFactor: _animation, axis: Axis.vertical, axisAlignment: 1,
3
child: Center( child: ClipRRect(
4
borderRadius: BorderRadius.circular(100),
5
child: Container(height: 200, width: 200, color: Colors.red)),
6
),
7
),
8
);
9
}

AnimatedWidget

Nous venons déjà d'étudier certains AnimatedWidget à travers des extensions déjà disponibles dans flutter. Mais si vous n'y avez pas encore trouvé votre bonheur, il est tout à fait possible de créer vos propres extensions de la classe AnimatedWidget !

Fondamental

Nous allons maintenant essayer de créer notre propre AnimatedWidget de la classe Button, puisque la classe « AnimatedButton » n'existe pas encore.

1
Class ButtonTransition extends AnimatedWidget { @override
2
Widget build(BuildContext context) { (...)
3
 
4
}
5
}

Vous venez de créer votre propre AnimatedButton ! Toutofois, pour le moment, il ne fait pas grand chose. Pour y remédier, l'objectif est de faire varier la bordure extérieure de votre bouton. Pour ce faire, il est nécessaire de return un bouton (ici OutlineButton) mais aussi d'indiquer à votre AnimatedWidget à quel changement de paramètre il doit s'animer.

Ici, puisque nous souhaitons faire varier la largeur de la bordure, nous indiquons au widget que width est un listenable.

Ensuite, nous n'avons plus qu'à mettre width comme variable de largeur dans le paramètre borderSide du bouton, et le tour est joué !

1
Class ButtonTransition extends AnimatedWidget {
2
Const ButtonTransition({width} : super(listenable: width);
3
Animation<double> get width => listenable @override
4
Widget build(BuildContext context) { return OutlineButton(
5
child: Text(‘Appuie !'),
6
borderSide: BoderSide(width: width.value),
7
);
8
}
9
}

Maintenant, vous n'avez plus qu'à utiliser votre AnimatedButton comme n'importe quel autre AnimatedWidget « built-in » !

AnimatedBuilder

Définition

Un AnimatedBuilder est utile pour les widgets plus complexes qui souhaitent inclure une animation comme partie d'une fonction plus large.

Fondamental

La création d'un AnimatedBuilder ressemble beaucoup à la création d'un AnimatedWidget, du moins dans les premières étapes : l'ajout d'un SingleTickerProviderStateMixin, l'initialisation d'un controller, etc.

Ici, nous allons faire tourner un container sur lui-même.

1
class _MyStatefulWidgetState extends State<MyStatefulWidget> with SingleTickerProviderStateMixin {
2
AnimationController _controller;
3
4
@override
5
void initState() { super.initState();
6
_controller = AnimationController( duration: const Duration(seconds: 5), vsync: this,
7
)..repeat();
8
}
9
10
@override void dispose() {
11
_controller.dispose(); super.dispose();
12
}
13
14
@override
15
Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller,
16
child: Container(width: 250.0, height: 250.0, color: Colors.purple), builder: (BuildContext context, Widget child) {
17
return Transform.rotate(
18
angle: _controller.value * 2.0 * math.pi, child: child,
19
);
20
},
21
);
22
}
23
}

Le builder a reçu en paramètre le child défini plus haut, à savoir le Container. Comme nous l'avons dit plus haut, la valeur d'un controller varie entre 0 et 1. Nous utilisons donc cette valeur pour savoir où en est le controller, mais aussi pour déterminer où doit en être la rotation de notre Container avec « angle: _controller.value * 2.0 * math.pi ».

Remarque

Il est possible de faire à peu près les mêmes choses avec un AnimatedBuilder qu'avec un AnimatedWidget. Le type que vous choisirez sera plus une question de goût ! La plupart du temps, l'AnimatedBuilder peut vite représenter un certain nombre de lignes de codes, alors qu'un AnimatedWidget pourrait faire le même travail en moins de lignes.