Widget Tree est une structure hiérarchique de widgets dans Flutter qui définit la disposition de l'interface utilisateur. Chaque élément de l'interface, d'un bouton à un écran entier, est représenté par un widget séparé imbriqué dans un conteneur parent. Flutter met à jour le Widget Tree à chaque changement d'état — le framework compare l'arbre nouveau et l'ancien et applique des modifications minimales. Selon Flutter Team, 2025, une structure d'arbre efficace affecte directement la fluidité des animations et la réactivité de l'interface.
Points clés
Widget Tree est une description déclarative de l'interface utilisateur dans Flutter, construite sous forme d'arbre de widgets imbriqués. Chaque widget définit une partie de l'UI : sa configuration, ses paramètres d'affichage et son comportement lors de l'interaction. Le développeur décrit à quoi doit ressembler l'interface dans l'état actuel de l'application, et Flutter se charge de convertir cette description en pixels à l'écran.
Contrairement aux frameworks impératifs où le développeur manipule directement les éléments de l'interface, Flutter utilise une approche déclarative. Lorsque l'état de l'application change, un nouveau Widget Tree est créé et le framework calcule la différence entre l'arbre nouveau et l'ancien. Cela minimise le nombre d'opérations de rendu et rend le code plus prévisible.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("Widget Tree")),
body: Center(
child: Column(
children: [
Text("Bonjour, Flutter"),
ElevatedButton(
onPressed: () {},
child: Text("Appuyez-moi"),
),
],
),
),
),
);
}
}
Dans cet exemple, le Widget Tree se compose de MaterialApp, Scaffold, AppBar, Center, Column, Text et ElevatedButton. Chacun de ces widgets est un nœud de l'arbre. Lorsque l'état de l'application change, Flutter appelle à nouveau la méthode build et compare le résultat avec l'arbre précédent.
Le Widget Tree commence par un widget racine passé à la méthode runApp. Le widget racine est généralement MaterialApp, CupertinoApp ou WidgetsApp — il définit les paramètres globaux de l'application. À partir de la racine, l'arbre se ramifie en widgets enfants, chacun pouvant contenir ses propres descendants.
Les widgets dans Flutter sont divisés en single-child (acceptent un seul enfant via le paramètre child) et multi-child (acceptent une liste d'enfants via children). Exemples de single-child : Center, Padding, SizedBox, Container. Multi-child : Column, Row, Stack, ListView, GridView. Cette différence affecte la structure du Widget Tree : les widgets multi-child créent des arbres plus larges, tandis que les single-child créent des arbres plus profonds.
BuildContext est l'emplacement d'un widget dans le Widget Tree. Chaque widget a son propre BuildContext, qui est passé à la méthode build et utilisé pour accéder aux widgets parents, au thème, à MediaQuery et aux autres InheritedWidgets. BuildContext sert de pont entre le widget et son élément dans l'Element Tree.
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final mediaQuery = MediaQuery.of(context);
return Container(
color: theme.colorScheme.primary,
child: Text(
"Largeur de l'écran : ${mediaQuery.size.width}"
),
);
}
}
Dans cet exemple, BuildContext est utilisé pour obtenir le thème et les dimensions de l'écran. Flutter remonte le Widget Tree jusqu'au Theme et MediaQuery les plus proches, qui sont des InheritedWidgets. Cela démontre comment le contexte connecte un widget à sa position dans la hiérarchie.
Lorsqu'une application Flutter démarre, la fonction runApp est appelée, qui prend le widget racine et commence à construire le Widget Tree. Le processus comprend trois étapes : création de la configuration des widgets, formation de l'Element Tree et construction du RenderObject Tree pour le rendu réel.
La fonction runApp crée un élément racine via WidgetsFlutterBinding, qui connecte le framework au moteur graphique. Le widget racine est placé dans l'arbre, et Flutter appelle la méthode build pour le remplir avec des widgets enfants. Chaque appel de build génère un nouveau sous-graphe du Widget Tree.
Après la construction du Widget Tree, Flutter effectue un layout initial — calculant les tailles et positions de tous les widgets. Ce processus commence à partir de la racine et se propage vers le bas de l'arbre. Chaque widget reçoit des contraintes de son parent et retourne une taille calculée. Si les tailles ne correspondent pas, Flutter génère une erreur de layout.
Après la fin du layout, Flutter procède au rendu de chaque widget. Le RenderObject convertit la description de l'interface en commandes graphiques exécutées par le GPU via Skia ou Impeller. L'ensemble du processus — du Widget Tree aux pixels — se répète à chaque changement d'état jusqu'à 120 images par seconde.
StatelessWidget est un widget qui n'a pas d'état interne mutable. Son apparence est entièrement déterminée par les paramètres d'entrée passés via le constructeur. Si les paramètres n'ont pas changé, StatelessWidget n'est pas reconstruit. Cela le rend léger en termes de performances.
Utilisez StatelessWidget pour les éléments d'interface statiques : icônes, étiquettes de texte, séparateurs décoratifs et boutons simples sans logique interne. Selon la documentation Flutter, environ 70 % des widgets dans une application typique peuvent être StatelessWidget, réduisant la charge du ramasse-miettes et accélérant les rebuilds.
StatefulWidget crée un objet State qui persiste entre les reconstructions du widget. Lorsque l'état change (via setState), Flutter marque le widget comme “sale” et le reconstruit à l'image suivante. StatefulWidget permet des éléments interactifs : champs de saisie, animations, minuteries et listes dynamiques.
class CounterWidget extends StatefulWidget {
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text("Compteur : $_count"),
ElevatedButton(
onPressed: () {
setState(() => _count++);
},
child: Text("Incrémenter"),
),
],
);
}
}
Dans cet exemple, le StatefulWidget utilise setState pour mettre à jour le compteur. Lorsque l'état est mis à jour, Flutter reconstruit uniquement la partie modifiée du Widget Tree — le CounterWidget et ses descendants. Les widgets parents ne sont pas reconstruits, ce qui est un avantage clé du modèle déclaratif de Flutter.
Le Widget Tree est la couche de configuration, tandis que l'Element Tree est le lien intermédiaire entre les widgets et le rendu réel. Chaque widget dans le Widget Tree crée un élément dans l'Element Tree, qui stocke une référence au widget et gère son cycle de vie. Cette architecture permet à Flutter de gérer les changements efficacement.
Lorsque Flutter rencontre un widget pour la première fois, il appelle la méthode createElement, qui crée un élément correspondant. Pour StatelessWidget, un StatelessElement est créé ; pour StatefulWidget, un StatefulElement est créé, qui instancie également un objet State. L'élément persiste entre les cycles de reconstruction, même si le widget est recréé.
Key est un identifiant qui aide Flutter à faire correspondre les widgets de l'ancien et du nouveau Widget Tree. Si un widget a une Key, Flutter l'utilise pour trouver l'élément correspondant plutôt que sa position dans l'arbre. Les clés sont nécessaires lors du travail avec des listes dynamiques où l'ordre des éléments peut changer.
ListView(
children: items.map((item) => ListItem(
key: ValueKey(item.id),
data: item,
)).toList(),
)
Sans Key, Flutter fait correspondre les éléments par position, ce qui peut entraîner une mauvaise préservation de l'état lors du changement d'ordre. ValueKey avec un identifiant unique garantit que chaque élément conserve son état indépendamment de sa position dans la liste.
La structure du Widget Tree affecte directement les performances des applications Flutter. Les arbres profonds avec de nombreux widgets imbriqués nécessitent plus de temps pour la phase de layout et augmentent l'utilisation de la mémoire. Flutter DevTools fournit des outils pour analyser le Widget Tree et identifier les goulots d'étranglement.
Chaque niveau d'imbrication ajoute des calculs supplémentaires lors du layout et du paint. Au lieu d'une imbrication profonde en chaîne, utilisez des structures plus plates. Par exemple, Row avec Expanded peut remplacer plusieurs Containers imbriqués avec Align. Selon Flutter Team, l'optimisation de l'arbre peut réduire le temps de layout jusqu'à 40 %.
Flutter DevTools fournit l'outil “Widget Inspector”, qui montre le Widget Tree actuel en temps réel. Le développeur peut sélectionner n'importe quel widget à l'écran et voir sa place dans l'arbre, ses paramètres et ses contraintes de layout. Cela aide à identifier les imbrications inattendues, les rebuilds excessifs et les problèmes de dimensionnement.
RepaintBoundary est un widget qui isole une partie du Widget Tree pour un rendu indépendant. Si le contenu à l'intérieur de RepaintBoundary change, seule sa zone est repeinte, pas tout l'écran. Utilisez RepaintBoundary pour les animations, les listes et autres éléments fréquemment mis à jour.
RepaintBoundary(
child: CustomPaint(
painter: MyPainter(),
child: SizedBox(
width: 200,
height: 200,
),
),
)
Dans cet exemple, RepaintBoundary isole CustomPaint dans une zone de rendu séparée. Lorsque l'animation à l'intérieur de cette zone est mise à jour, seul le widget CustomPaint est repeint, tandis que le reste de l'écran reste inchangé. Ceci est particulièrement utile dans les interfaces complexes avec plusieurs éléments animés.
Questions fréquentes
Le Widget Tree est une description déclarative de l'interface qui est recréée à chaque rebuild. L'Element Tree persiste entre les mises à jour et gère le cycle de vie, l'état et la correspondance des widgets avec les RenderObjects réels.
Il n'y a pas de limite au nombre de widgets, mais en pratique un arbre avec des milliers de widgets peut ralentir la phase de layout. Flutter est optimisé pour des arbres allant jusqu'à plusieurs milliers de nœuds ; pour des nombres plus importants, la virtualisation via ListView.builder est recommandée.
Utilisez Flutter DevTools — l'onglet “Widget Inspector”. Lancez l'application en mode debug, ouvrez DevTools dans le navigateur et sélectionnez n'importe quel widget à l'écran pour voir sa place dans le Widget Tree.
Un rebuild est le processus de recréation de la configuration des widgets lorsque l'état change. Flutter appelle à nouveau la méthode build pour les widgets modifiés, compare le nouveau Widget Tree avec le précédent et applique des modifications minimales à l'Element Tree.
Réduisez la profondeur d'imbrication, utilisez des const widgets pour les éléments statiques, appliquez RepaintBoundary pour isoler les animations et évitez les StatefulWidgets excessifs là où StatelessWidget suffit.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi