Skip to content

Flutter Layouts and Widgets

Flutter propose plusieurs widgets puissants pour créer des mises en page complexes et réactives. Ce guide couvre les concepts essentiels des layouts Flutter, des widgets de base aux principes avancés de contraintes et de positionnement.

Un grand nombre de mises en pages peuvent être construites à l’aide des Row et Column, un peu à la manière des display:flex en CSS.

Ces deux classes permettent d’aligner leur contenu à l’aide des propriétés :

  • mainAxisAlignment
  • et crossAxisAlignment

Il est également possible de paramètrer la taille occupée par la ligne/column à l’aide de mainAxisSize.

Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Icon(Icons.add),
Text('Ajouter'),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Icon(Icons.edit),
Text('Modifier'),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Icon(Icons.remove),
Text('Supprimer'),
],
),
],
)

Le widget Wrap permet de disposer des éléments dans une direction comme les Row et les Columns, mais sur plusieurs “lignes”. Lorsqu’un élément ne peut être ajouté à la ligne courante, il passe “à la ligne”.

Scaffold(
backgroundColor: Colors.blueGrey[200],
appBar: AppBar(),
body: SizedBox.expand(
child: Container(
color: Colors.yellow,
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
/*runSpacing: 30,*/
spacing: 30,
runAlignment: WrapAlignment.spaceEvenly,
/*direction: Axis.vertical,*/
verticalDirection: VerticalDirection.up,
children: List.generate(20, (index) => index)
.map(
(e) => Container(
padding: const EdgeInsets.all(12),
color: Colors.cyan,
child: Text('Item $e')),
)
.toList(),
),
),
),
);
Wrap(
alignment: WrapAlignment.end,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.end,
direction: Axis.horizontal,
children: List.generate(
12,
(index) => Container(
height: 60 + (5.0 * index),
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.all(12),
color: Colors.grey.shade300,
child: Text('item ${index + 1}'),
),
),
)

Documentation officielle Wrap

Il est possible d’étendre l’espace accordé à un élément de Row ou de Column.

Expanded : s’étend “autant que faire se peut”

Row(
children: [
Expanded(
child: Container(
color: Colors.red,
child: Text('Expanded 1'),
),
),
Expanded(
child: Container(
color: Colors.blue,
child: Text('Expanded 2'),
),
),
],
)

Flexible : permet de définir un coefficient de flexibilité flex

Row(
children: [
Flexible(
flex: 1,
child: Container(
color: Colors.red,
child: Text('Flexible 1'),
),
),
Flexible(
flex: 2,
child: Container(
color: Colors.blue,
child: Text('Flexible 2'),
),
),
],
)

Spacer : permet d’inclure un espacement, qui occupera la totalité de l’espace disponible, ou un coefficient de flexibilité flex

Row(
children: [
Text('Début'),
Spacer(),
Text('Fin'),
],
)

Stack crée une pile de widgets, positionnés via Positioned ou Align.

SizedBox(
width: 64,
height: 64,
child: Stack(
children: const [
Positioned(
top: 2,
child: Icon(
Icons.account_circle_rounded,
size: 54,
color: Colors.orange,
),
),
Positioned(
right: 4,
child: Icon(
Icons.add_circle_outlined,
color: Colors.green,
size: 24,
),
),
],
),
)
Stack(
children: [
Image.network(
'https://fr.web.img4.acsta.net/r_654_368/newsv7/21/01/11/16/11/2598562.jpg',
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
height: 74,
padding: const EdgeInsets.all(8),
color: Colors.black54,
child: RichText(
text: const TextSpan(children: [
TextSpan(
text: 'Spiderman\n',
style: TextStyle(color: Colors.white, fontSize: 24),
),
TextSpan(
text: '2007',
style: TextStyle(color: Colors.grey, fontSize: 14),
),
]),
),
),
),
const Positioned(
right: 8,
top: 8,
child: Icon(Icons.favorite, color: Colors.red, size: 42))
],
)

Flutter propose des widgets animés pour le positionnement :

AnimatedPositioned(
right: position,
top: 8,
duration: const Duration(seconds: 1),
child: IconButton(
icon: Icon(Icons.favorite, color: Colors.red, size: 42),
onPressed: () => setState(() => position = -20),
),
)

En savoir plus sur Stack

“Constraints go down, Size go up and parents sets position”

Une contrainte permet au parent d’un widget, d’exprimer une taille minimale et une taille maximale disponible pour le widget. Le widget enfant détermine ensuite sa propre taille.

  • tight : assigner une taille fixe
  • loosen : définit uniquement une taille maximale

Documentation officielle sur les contraintes

Gestion des différentes tailles d’écrans

Section titled “Gestion des différentes tailles d’écrans”

Flutter propose plusieurs manières de gérer les différentes tailles d’écrans :

  • MediaQuery.of(context).size / MediaQuery.sizeOf(context)
  • MediaQuery.of(context).orientation / MediaQuery.orientationOf(context)
final screenSize = MediaQuery.of(context).size;
final orientation = MediaQuery.of(context).orientation;
Container(
width: screenSize.width * 0.8,
height: orientation == Orientation.portrait ? 200 : 100,
child: Text('Responsive Container'),
)

LayoutBuilder permet de construire des widgets en fonction des contraintes disponibles.

LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return Row(children: widgets);
} else {
return Column(children: widgets);
}
},
)

Widget catalog : Layout

Flutter favorise la composition de widgets simples plutôt que l’utilisation de widgets complexes comme Container.

// ✅ Recommandé : Composition
Padding(
padding: EdgeInsets.all(8.0),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Text('Hello'),
),
)
// ⚠️ Acceptable mais moins flexible
Container(
padding: EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Text('Hello'),
)
  • Padding : exprimé à l’aide de EdgeInsets
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Padding(
padding: EdgeInsets.all(8.0),
child: Icon(Icons.add),
),
Text('Ajouter'),
],
)
  • Center : centre son enfant
  • SizedBox : définit une taille fixe ou crée un espacement
  • AspectRatio : maintient un ratio largeur/hauteur

Le widget Container a la particularité d’aller à l’encontre du principe de composition. En effet, ce widget intègre la logique de plusieurs autres widgets, et permet de déclarer :

  • un padding
  • une marge
  • un alignment
  • une taille : la largeur width et la hauteur height
  • une couleur color
  • une decoration de type BoxDecoration, pouvant définir :
    • un borderRadius
    • un contour
  • une contrainte constraints
Container(
width: 300,
height: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.red, width: 3),
boxShadow: const [BoxShadow(blurRadius: 8, spreadRadius: 2)],
gradient: LinearGradient(colors: [Colors.cyan, Colors.blueGrey]),
),
alignment: Alignment.center,
child: Text(
'Hello, World!',
style: Theme.of(context).textTheme.headline4,
),
);

Card est un container Material ombré et légèrement arrondi :

Card(
elevation: 5,
color: Colors.grey.shade300,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text('Settings : $category'),
),
ElevatedButton(
onPressed: context.pop,
child: const Text('Fermer'),
)
],
),
),
)
  1. Utilisez la composition : Préférez composer plusieurs widgets simples plutôt qu’utiliser un widget complexe
  2. Optimisez les performances : Utilisez const constructors quand possible
  3. Gérez les contraintes : Comprenez comment les contraintes se propagent dans l’arbre de widgets
  4. Testez sur différentes tailles : Utilisez des outils comme MediaQuery et LayoutBuilder pour créer des layouts responsives
  5. Évitez les overflows : Utilisez Flexible, Expanded, ou Wrap pour gérer le contenu dynamique