Les Animations Implicites dans Flutter
Sommaire
- 1- Objectifs
- 2- Introduction aux Animations implicites
- 3- Animations Implicites :
- 4- Exemples de widgets d'animations implicites :
- 4.1- AnimatedBuilder
- 4.1.1- Description :
- 4.1.2- Syntaxe :
- 4.1.3- Exemple :
- 4.2- AnimatedContainer
- 4.2.1- Description :
- 4.2.2- Syntaxe :
- 4.2.3- Exemple :
- 4.3- AnimatedOpacity
- 4.3.1- Description :
- 4.3.2- Syntaxe :
- 4.3.3- Exemple :
- 4.4- AnimatedAlign
- 4.4.1- Description :
- 4.4.2- Syntaxe :
- 4.4.3- Exemple :
- 4.5- AnimatedPadding
- 4.5.1- Description :
- 4.5.2- Syntaxe :
- 4.5.3- Exemple :
- 4.6- AnimatedPositioned
- 4.6.1- Description :
- 4.6.2- Syntaxe :
- 4.6.3- Exemple :
- 4.7- AnimatedRotation
- 4.7.1- Description :
- 4.7.2- Syntaxe :
- 4.7.3- Exemple :
- 4.8- AnimatedScale
- 4.8.1- Description :
- 4.8.2- Syntaxe :
- 4.8.3- Exemple :
- 4.9- AnimatedSize
- 4.9.1- Description :
- 4.9.2- Syntaxe :
- 4.9.3- Exemple :
- 4.10- AnimatedSlide
- 4.10.1- Description :
- 4.10.2- Syntaxe :
- 4.10.3- Exemple :
- 4.11- AnimatedSwitcher
- 4.11.1- Description :
- 4.11.2- Syntaxe :
- 4.11.3- Exemple :
- 5- Applications
- 5.1- Exercice:01
- 5.2- Exercice_02 : Création d’un bouton animé avec AnimatedContainer
- 5.3- Exercice_03 : Transition de l'opacité avec
AnimatedOpacity
- 5.4- Exercice_04 : Création d'une boîte qui change de position avec AnimatedPositioned
- 5.5- Exercice_05 :
- 5.5.1- Cours Flutter
Les Animations Implicites dans Flutter
-
Objectifs
- Comprendre le concept des animations implicites
- Savoir implémenter des animations implicites
- Manipuler plusieurs propriétés animées simultanément
-
Introduction aux Animations implicites
- Les animations implicites dans Flutter sont des animations où le framework gère automatiquement la transition entre l’état initial et l’état final d’une propriété. Cela simplifie le processus d’animation, car il suffit de définir une nouvelle valeur de propriété, et Flutter se charge d’animer la transition de manière fluide.
-
Animations Implicites :
- Concept : Les animations implicites sont des animations où le développeur définit uniquement la nouvelle valeur de la propriété à animer (comme la taille, la couleur, l’opacité, etc.), et Flutter se charge de calculer la transition entre l’état initial et l’état final de manière fluide.
- Pourquoi implicite ? Elles sont dites implicites parce que les calculs d’interpolation entre l’état initial et l’état final sont faits par le framework automatiquement, sans besoin de gestion manuelle de l’animation ou du contrôle de chaque étape.
- Certains widgets animés intégrés sont :
AnimatedAlign
AnimatedBuilder
AnimatedContainer
AnimatedOpacity
AnimatedPadding
AnimatedPositioned
AnimatedRotation
AnimatedScale
AnimatedSize
AnimatedSlide
AnimatedSwitcher
-
Exemples de widgets d’animations implicites :
-
AnimatedBuilder
-
Description :
- AnimatedBuilder est un widget qui vous permet d’utiliser n’importe quel widget pour créer des animations personnalisées. Il prend un Animation et un builder, et met à jour le widget à chaque changement d’animation.
- Il est utilisé principalement pour réutiliser des widgets enfants sans avoir besoin de les recréer à chaque changement d’animation.
-
Syntaxe :
-
Exemple :
-
AnimatedContainer
-
Description :
- Ce widget permet d’animer plusieurs propriétés telles que la taille, la couleur, la bordure, etc. Lorsque ces propriétés changent, Flutter anime la transition entre les anciennes et nouvelles valeurs.
- AnimatedContainer anime les changements de ses propriétés telles que la taille, la couleur, la forme, les marges, etc. Vous pouvez modifier ces propriétés, et Flutter animera automatiquement la transition entre les anciennes et les nouvelles valeurs.
-
Syntaxe :
-
Exemple :
-
AnimatedOpacity
-
Description :
- Ce widget anime l’opacité d’un widget, permettant de faire apparaître ou disparaître un élément progressivement.
- AnimatedOpacity anime les changements d’opacité d’un widget. Vous pouvez modifier la valeur d’opacité entre 0.0 (totalement transparent) et 1.0 (totalement opaque), et Flutter animera la transition en douceur.
-
Syntaxe :
-
Exemple :
-
AnimatedAlign
-
Description :
- Ce widget permet d’animer l’alignement d’un widget à l’intérieur de son conteneur.
- AnimatedAlign anime l’alignement d’un widget à l’intérieur de son parent. Vous pouvez spécifier l’alignement (haut, bas, centre, etc.), et Flutter animera la transition entre les différentes positions.
-
Syntaxe :
-
Exemple :
-
AnimatedPadding
-
Description :
- Ce widget anime les marges internes (padding) d’un widget lorsque ces valeurs changent.
- AnimatedPadding anime les changements de padding (espacement) autour d’un widget. Vous pouvez modifier les valeurs de padding, et Flutter animera la transition.
-
Syntaxe :
-
Exemple :
-
AnimatedPositioned
-
Description :
- AnimatedPositioned anime la position d’un widget dans un Stack quand la position change.
- Idéal pour animer des mouvements dans une interface.
-
Syntaxe :
-
Exemple :
-
AnimatedRotation
-
Description :
- AnimatedRotation anime la rotation d’un widget en fonction d’une valeur d’angle spécifiée en radians.
-
Syntaxe :
-
Exemple :
-
AnimatedScale
-
Description :
- AnimatedScale anime le changement de taille (zoom) d’un widget en fonction d’un facteur de mise à l’échelle.
-
Syntaxe :
-
Exemple :
-
AnimatedSize
-
Description :
- AnimatedSize anime le changement de taille d’un widget lorsque sa taille change.
- Nécessite l’ajout de vsync: this dans l’état du widget.
-
Syntaxe :
-
Exemple :
-
AnimatedSlide
-
Description :
- AnimatedSlide anime la position d’un widget sur un axe cartésien (x, y), où chaque valeur est un pourcentage de la taille de l’écran.
-
Syntaxe :
-
Exemple :
-
AnimatedSwitcher
-
Description :
- AnimatedSwitcher permet d’animer la transition entre différents widgets enfants. Lorsque le widget enfant change, AnimatedSwitcher anime l’ajout du nouveau widget et la suppression de l’ancien.
-
Syntaxe :
-
Exemple :
-
Applications
-
Exercice:01
- Enoncé:
- Objectif : L’objectif de cet exercice est de permettre à l’étudiant d’apprendre à utiliser des animations implicites dans Flutter à travers des widgets comme AnimatedContainer, AnimatedOpacity, AnimatedAlign, et AnimatedPadding. L’étudiant devra combiner ces widgets pour créer une interface réactive et animée.
- Consignes :
- Créez une application Flutter qui contient une carte (Card) centrée à l’écran.
- La carte devra :
- Changer de taille lorsqu’on appuie dessus.
- Modifier son alignement (se déplacer d’un coin à l’autre de l’écran).
- Changer son opacité (de totalement opaque à semi-transparent).
- Ajuster son padding pour laisser plus ou moins d’espace autour d’elle.
- L’étudiant devra utiliser :
- AnimatedContainer pour la taille et la couleur.
- AnimatedOpacity pour la transparence.
- AnimatedAlign pour l’alignement.
- AnimatedPadding pour les marges autour de la carte.
- L’animation doit être déclenchée par un tap sur la carte.
-
Exercice_02 : Création d’un bouton animé avec AnimatedContainer
- Enoncé:
- Animer un bouton Flutter qui change de taille, de couleur, et de bordure lorsqu’il est cliqué. Créez un bouton au centre de l’écran. Lorsqu’un utilisateur clique sur le bouton, la taille doit doubler, sa couleur doit changer, et ses bordures doivent devenir arrondies. Utilisez AnimatedContainer pour gérer les transitions entre les différentes propriétés du bouton.
- Utilisez un
AnimatedContainer
pour définir la taille, la couleur et la bordure du bouton, et employezsetState
pour modifier les propriétés à animer lors du clic sur le bouton. Ajoutez des courbes d’animation commeCurves.easeIn
ouCurves.bounceOut
pour observer leur effet sur les transitions. -
Exercice_03 : Transition de l’opacité avec
AnimatedOpacity
- Enoncé:
- Créer une animation Flutter qui masque et révèle un widget en douceur.
- Ajoutez un texte ou une image à l’écran.
- Utilisez AnimatedOpacity pour faire disparaître et réapparaître le widget lorsque l’utilisateur clique sur un bouton.
- L’opacité doit passer de 1 (complètement visible) à 0 (invisible) en douceur.
- Utilisez un AnimatedOpacity pour contrôler la transparence du widget.
- Associez un bouton pour déclencher l’animation avec un setState qui change l’opacité.
- Expérimentez avec la durée de l’animation pour obtenir différents effets de fluidité.
-
Exercice_04 : Création d’une boîte qui change de position avec AnimatedPositioned
- Enoncé:
- Créez un widget positionné (Positioned) dans un Stack. Lorsque l’utilisateur appuie sur un bouton, la boîte doit se déplacer vers une nouvelle position à l’aide de AnimatedPositioned.
- Utilisez Stack pour organiser plusieurs widgets avec des positions définies. Modifiez les propriétés left, right, top, et bottom de la boîte lorsque l’utilisateur interagit.
- Employez AnimatedPositioned pour que le changement de position se fasse en douceur.
-
Exercice_05 :
- Enoncé:
AnimatedBuilder({
required Animation animation,
required Widget Function(BuildContext, Widget?) builder,
Widget? child,
});
class AnimatedBuilderExample extends StatefulWidget {
@override
_AnimatedBuilderExampleState createState() => _AnimatedBuilderExampleState();
}
class _AnimatedBuilderExampleState extends State with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.rotate(
angle: _controller.value * 2.0 * 3.14,
child: child,
);
},
child: Container(width: 100, height: 100, color: Colors.blue),
);
}
}
AnimatedContainer({
required Duration duration,
double? width,
double? height,
Color? color,
BoxDecoration? decoration,
EdgeInsetsGeometry? padding,
Curve curve = Curves.linear,
required Widget child,
});
class AnimatedContainerExample extends StatefulWidget {
@override
_AnimatedContainerExampleState createState() => _AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState extends State {
bool _isExpanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
child: AnimatedContainer(
width: _isExpanded ? 200 : 100,
height: _isExpanded ? 200 : 100,
color: _isExpanded ? Colors.blue : Colors.red,
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
child: Center(child: Text('Tap me!')),
),
);
}
}
AnimatedOpacity({
required double opacity,
required Duration duration,
Curve curve = Curves.linear,
required Widget child,
});
class AnimatedOpacityExample extends StatefulWidget {
@override
_AnimatedOpacityExampleState createState() => _AnimatedOpacityExampleState();
}
class _AnimatedOpacityExampleState extends State {
double _opacity = 1.0;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedOpacity(
opacity: _opacity,
duration: Duration(seconds: 1),
child: Container(width: 100, height: 100, color: Colors.green),
),
ElevatedButton(
onPressed: () {
setState(() {
_opacity = _opacity == 1.0 ? 0.0 : 1.0;
});
},
child: Text('Toggle Opacity'),
),
],
);
}
}
AnimatedAlign({
required Alignment alignment,
required Duration duration,
Curve curve = Curves.linear,
required Widget child,
});
class AnimatedAlignExample extends StatefulWidget {
@override
_AnimatedAlignExampleState createState() => _AnimatedAlignExampleState();
}
class _AnimatedAlignExampleState extends State {
bool _isAligned = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_isAligned = !_isAligned;
});
},
child: Container(
height: 300,
width: 300,
color: Colors.grey[300],
child: AnimatedAlign(
alignment: _isAligned ? Alignment.topRight : Alignment.bottomLeft,
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
child: Container(width: 50, height: 50, color: Colors.blue),
),
),
);
}
}
AnimatedPadding({
required EdgeInsetsGeometry padding,
required Duration duration,
Curve curve = Curves.linear,
required Widget child,
});
class AnimatedPaddingExample extends StatefulWidget {
@override
_AnimatedPaddingExampleState createState() => _AnimatedPaddingExampleState();
}
class _AnimatedPaddingExampleState extends State {
double _paddingValue = 10.0;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedPadding(
padding: EdgeInsets.all(_paddingValue),
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
child: Container(width: 100, height: 100, color: Colors.red),
),
ElevatedButton(
onPressed: () {
setState(() {
_paddingValue = _paddingValue == 10.0 ? 50.0 : 10.0;
});
},
child: Text('Change Padding'),
),
],
);
}
}
AnimatedPositioned({
required Widget child,
required Duration duration,
double? left,
double? top,
double? right,
double? bottom,
Curve curve = Curves.linear,
});
class AnimatedPositionedExample extends StatefulWidget {
@override
_AnimatedPositionedExampleState createState() => _AnimatedPositionedExampleState();
}
class _AnimatedPositionedExampleState extends State {
bool _isMoved = false;
@override
Widget build(BuildContext context) {
return Stack(
children: [
AnimatedPositioned(
duration: Duration(seconds: 2),
left: _isMoved ? 150 : 50,
top: _isMoved ? 200 : 50,
child: GestureDetector(
onTap: () {
setState(() {
_isMoved = !_isMoved;
});
},
child: Container(width: 100, height: 100, color: Colors.red),
),
),
],
);
}
}
AnimatedRotation({
required double turns,
required Duration duration,
Curve curve = Curves.linear,
required Widget child,
});
AnimatedRotation(
turns: 0.5, // 180-degree rotation
duration: Duration(seconds: 1),
child: Icon(Icons.rotate_right, size: 100),
);
AnimatedScale({
required double scale,
required Duration duration,
Curve curve = Curves.linear,
required Widget child,
});
AnimatedScale(
scale: 1.5, // Increases size by 1.5x
duration: Duration(seconds: 1),
child: Container(width: 100, height: 100, color: Colors.blue),
);
AnimatedSize({
required Widget child,
required Duration duration,
Curve curve = Curves.linear,
required TickerProvider vsync,
});
class AnimatedSizeExample extends StatefulWidget {
@override
_AnimatedSizeExampleState createState() => _AnimatedSizeExampleState();
}
class _AnimatedSizeExampleState extends State with TickerProviderStateMixin {
bool _isExpanded = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
child: AnimatedSize(
duration: Duration(seconds: 1),
vsync: this,
child: Container(
width: 100,
height: _isExpanded ? 200 : 100,
color: Colors.green,
),
),
);
}
}
AnimatedSlide({
required Offset offset,
required Duration duration,
Curve curve = Curves.linear,
required Widget child,
});
AnimatedSlide(
offset: Offset(0.2, 0.0), // Move 20% of the screen width to the right
duration: Duration(seconds: 1),
child: Container(width: 100, height: 100, color: Colors.orange),
);
AnimatedSwitcher({
required Widget child,
required Duration duration,
Curve switchInCurve = Curves.linear,
Curve switchOutCurve = Curves.linear,
required AnimatedSwitcherTransitionBuilder transitionBuilder,
});
class AnimatedSwitcherExample extends StatefulWidget {
@override
_AnimatedSwitcherExampleState createState() => _AnimatedSwitcherExampleState();
}
class _AnimatedSwitcherExampleState extends State {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
AnimatedSwitcher(
duration: Duration(seconds: 1),
child: Text(
'$_count',
key: ValueKey(_count),
style: TextStyle(fontSize: 50),
),
),
ElevatedButton(
onPressed: () {
setState(() {
_count++;
});
},
child: Text('Increment'),
),
],
);
}
}
Solution
import 'package:flutter/material.dart';
void main() {
runApp(AnimatedCardApp());
}
class AnimatedCardApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: AnimatedCardScreen(),
);
}
}
class AnimatedCardScreen extends StatefulWidget {
@override
_AnimatedCardScreenState createState() => _AnimatedCardScreenState();
}
class _AnimatedCardScreenState extends State {
bool _isExpanded = false;
bool _isAlignedTopRight = false;
double _opacity = 1.0;
double _paddingValue = 10.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Exercice d'animation"),
),
body: GestureDetector(
onTap: () {
setState(() {
_isExpanded = !_isExpanded;
_isAlignedTopRight = !_isAlignedTopRight;
_opacity = _opacity == 1.0 ? 0.5 : 1.0;
_paddingValue = _paddingValue == 10.0 ? 50.0 : 10.0;
});
},
child: AnimatedPadding(
padding: EdgeInsets.all(_paddingValue),
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
child: AnimatedAlign(
alignment: _isAlignedTopRight ? Alignment.topRight : Alignment.bottomLeft,
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
child: AnimatedOpacity(
opacity: _opacity,
duration: Duration(seconds: 1),
child: AnimatedContainer(
width: _isExpanded ? 200 : 100,
height: _isExpanded ? 200 : 100,
color: _isExpanded ? Colors.blue : Colors.red,
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
child: Center(child: Text('Tap me!')),
),
),
),
),
),
);
}
}
Solution
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: AnimatedButtonScreen(),
);
}
}
class AnimatedButtonScreen extends StatefulWidget {
@override
_AnimatedButtonScreenState createState() => _AnimatedButtonScreenState();
}
class _AnimatedButtonScreenState extends State {
double _width = 100;
double _height = 50;
Color _color = Colors.blue;
BorderRadiusGeometry _borderRadius = BorderRadius.circular(8);
void _animateButton() {
setState(() {
// Toggle between two states
_width = _width == 100 ? 200 : 100;
_height = _height == 50 ? 100 : 50;
_color = _color == Colors.blue ? Colors.red : Colors.blue;
_borderRadius = _borderRadius == BorderRadius.circular(8)
? BorderRadius.circular(30)
: BorderRadius.circular(8);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Animated Button Example')),
body: Center(
child: AnimatedContainer(
width: _width,
height: _height,
decoration: BoxDecoration(
color: _color,
borderRadius: _borderRadius,
),
duration: Duration(seconds: 1),
curve: Curves.easeIn, // You can change to Curves.bounceOut or others
child: TextButton(
onPressed: _animateButton,
child: Text(
'Click Me',
style: TextStyle(color: Colors.white, fontSize: 18),
),
),
),
),
);
}
}
Solution
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: AnimatedOpacityExample(),
);
}
}
class AnimatedOpacityExample extends StatefulWidget {
@override
_AnimatedOpacityExampleState createState() => _AnimatedOpacityExampleState();
}
class _AnimatedOpacityExampleState extends State {
// Propriété d'opacité
double _opacity = 1.0;
// Fonction pour changer l'opacité
void _toggleOpacity() {
setState(() {
// Alterne entre 1 (visible) et 0 (invisible)
_opacity = _opacity == 1.0 ? 0.0 : 1.0;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Animation Opacity'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// AnimatedOpacity pour animer la visibilité
AnimatedOpacity(
opacity: _opacity, // Gère la transparence du widget
duration: Duration(seconds: 2), // Durée de l'animation
curve: Curves.easeInOut, // Courbe de l'animation
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(
child: Text(
'Hello!',
style: TextStyle(
fontSize: 28,
color: Colors.white,
),
),
),
),
),
SizedBox(height: 20),
// Bouton pour déclencher l'animation
ElevatedButton(
onPressed: _toggleOpacity,
child: Text('Masquer / Révéler'),
),
],
),
),
);
}
}
Solution
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Déplacer une boîte')),
body: MovingBox(),
),
);
}
}
class MovingBox extends StatefulWidget {
@override
_MovingBoxState createState() => _MovingBoxState();
}
class _MovingBoxState extends State {
double _left = 50;
double _top = 50;
void _moveBox() {
setState(() {
// Modifiez les coordonnées pour déplacer la boîte
_left = _left == 50 ? 200 : 50;
_top = _top == 50 ? 200 : 50;
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
AnimatedPositioned(
duration: Duration(seconds: 1),
left: _left,
top: _top,
child: Container(
width: 100,
height: 100,
color: Colors.blue,
),
),
Positioned(
bottom: 30,
left: 30,
child: ElevatedButton(
onPressed: _moveBox,
child: Text('Déplacer la boîte'),
),
),
],
);
}
}
Solution