CHAPITRE 3.2

Interagir avec l'interface et modifier le contenu

Rendez votre application interactive avec des boutons et des actions utilisateur
Jusqu'à présent, vous avez créé des interfaces statiques qui affichent du contenu. Dans ce chapitre, nous allons découvrir comment rendre votre application interactive en ajoutant des boutons et en réagissant aux actions de l'utilisateur. Nous commencerons par apprendre à ajouter un bouton simple, puis nous découvrirons comment faire réagir l'interface aux interactions.

3.2Interagir avec l'interface et modifier le contenu dans Flutter

3.2.1 – Ajouter un bouton dans l'interface

Les boutons sont des éléments essentiels dans une application. Ils permettent à l'utilisateur d'interagir avec l'interface en déclenchant des actions. Dans Flutter, le widget ElevatedButton est le type de bouton le plus couramment utilisé.

🎯 Pourquoi utiliser un bouton

Les boutons permettent à l'utilisateur de :

  • Déclencher des actions : Valider un formulaire, sauvegarder des données, naviguer vers une autre page
  • Confirmer des choix : Accepter ou refuser une proposition
  • Interagir avec l'interface : Changer de page, ouvrir un menu, afficher des informations
Analogie :
Un bouton dans une application est comme un interrupteur dans une pièce : quand vous appuyez dessus, quelque chose se passe. Dans une application, appuyer sur un bouton déclenche une action que vous avez programmée.

🔘 Introduction à ElevatedButton

ElevatedButton est un widget qui affiche un bouton avec un effet d'élévation (ombre). C'est le type de bouton principal recommandé dans Material Design pour les actions importantes.

Material Design :
Material Design est le système de design de Google. ElevatedButton suit les règles de Material Design et offre un rendu professionnel et cohérent avec les autres applications Material.

Voici la structure de base d'un ElevatedButton :

ElevatedButton(
  onPressed: () {},
  child: Text('Mon bouton'),
)

Analysons ce code :

  • ElevatedButton( : Le widget qui crée le bouton
  • onPressed: () {} : La fonction qui sera appelée quand le bouton est pressé (pour l'instant, elle est vide)
  • child: Text('Mon bouton') : Le contenu du bouton (le texte affiché)
onPressed: () {} :
onPressed est un paramètre qui attend une fonction. La syntaxe () {} crée une fonction vide (qui ne fait rien pour l'instant). Nous verrons comment remplir cette fonction dans la prochaine section.

📝 Paramètres essentiels (child, onPressed)

ElevatedButton a deux paramètres essentiels :

  • child : Le contenu du bouton (généralement un Text, mais peut être n'importe quel widget)
  • onPressed : La fonction qui sera exécutée quand l'utilisateur appuie sur le bouton
child :
Le paramètre child définit ce qui sera affiché à l'intérieur du bouton. Le plus souvent, c'est un Text, mais vous pouvez aussi mettre une Row avec une icône et du texte, ou n'importe quel autre widget.
onPressed :
Le paramètre onPressed est obligatoire. Si vous ne le fournissez pas, le bouton sera désactivé (grisé). Pour l'instant, nous utilisons une fonction vide () {}, mais dans la prochaine section, nous verrons comment y mettre du code qui s'exécute quand le bouton est pressé.

🧪 Exemple simple : bouton affiché à l'écran

Voici un exemple complet avec un bouton affiché à l'écran :

Exemple ElevatedButton Flutter
Exemple visuel : un bouton ElevatedButton affiché au centre de l'écran.
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatelessWidget {
  const MaPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Mon Application'),
        ),
        body: SafeArea(
          child: Center(
            child: ElevatedButton(
              onPressed: () {},
              child: const Text('Cliquez-moi'),
            ),
          ),
        ),
      ),
    );
  }
}

Dans cet exemple, un bouton "Cliquez-moi" est affiché au centre de l'écran. Pour l'instant, appuyer sur le bouton ne fait rien car la fonction onPressed est vide.

Important :
Même si le bouton ne fait rien pour l'instant, il est important de fournir onPressed: () {}. Sans ce paramètre, le bouton serait désactivé (grisé) et l'utilisateur ne pourrait pas interagir avec.
Expérimenter :
Essayez de modifier le texte du bouton en changeant 'Cliquez-moi' par un autre texte. Vous verrez que le bouton s'adapte automatiquement à la taille du texte.

💡 Points clés à retenir

  • Les boutons permettent à l'utilisateur d'interagir avec l'application
  • ElevatedButton est le type de bouton principal recommandé
  • child définit le contenu du bouton (généralement un Text)
  • onPressed est la fonction exécutée quand le bouton est pressé
  • onPressed est obligatoire : sans lui, le bouton est désactivé
  • Pour l'instant, nous utilisons onPressed: () {} (fonction vide)
Résumé de la structure :
Voici la structure de base d'un ElevatedButton :
ElevatedButton(
  onPressed: () {},
  child: Text('Texte du bouton'),
)
Dans la prochaine section, nous verrons comment remplir la fonction onPressed pour que le bouton fasse quelque chose quand on appuie dessus.

3.2.2 – Gérer l'état avec StatefulWidget et setState()

Dans la section précédente, nous avons créé un bouton avec onPressed: () {}, mais ce bouton ne faisait rien. Pour créer une application vraiment interactive, nous devons pouvoir modifier l'interface en fonction des actions de l'utilisateur. C'est là qu'intervient StatefulWidget.

⚠️ Utilisation de onPressed & ses limites

Jusqu'à présent, nous avons utilisé onPressed: () {} avec une fonction vide. Même si nous ajoutons du code dans cette fonction, il y a une limitation importante :

ElevatedButton(
  onPressed: () {
    print('Bouton pressé !');
  },
  child: Text('Cliquez-moi'),
)

Dans cet exemple, quand vous appuyez sur le bouton, le message "Bouton pressé !" s'affichera dans la console, mais l'interface ne changera pas. C'est parce que StatelessWidget ne peut pas modifier son apparence après sa création.

Console :
La console est l'endroit où Flutter affiche les messages de débogage. Dans VS Code ou Android Studio, vous pouvez voir la console en bas de l'écran. Quand vous utilisez print(), le message apparaît dans cette console.
Limitation de StatelessWidget :
StatelessWidget est un widget qui ne peut pas changer après sa création. Une fois construit, il reste identique. C'est pourquoi, même si vous ajoutez du code dans onPressed, l'interface ne change pas visuellement.

🔄 Besoin d'un état pour modifier l'interface

Pour modifier l'interface (par exemple, changer le texte affiché ou incrémenter un compteur), nous avons besoin de stocker des informations qui peuvent changer. Ces informations s'appellent l'état (state) du widget.

Qu'est-ce que l'état ?
L'état d'un widget, c'est toutes les données qui peuvent changer et qui affectent l'apparence du widget. Par exemple :
  • Le nombre affiché dans un compteur (0, 1, 2, 3...)
  • Le texte d'un bouton qui change ("Cliquez-moi" → "Déjà cliqué")
  • Si une case est cochée ou non
  • La couleur d'un élément qui change
Quand l'état change, l'interface doit se mettre à jour pour refléter ce changement.

Pour gérer un état qui change, Flutter fournit StatefulWidget, qui est différent de StatelessWidget.

📊 Différence entre StatelessWidget et StatefulWidget

Voici les principales différences entre ces deux types de widgets :

Caractéristique StatelessWidget StatefulWidget
Peut changer ? Non, reste identique après création Oui, peut changer dynamiquement
Utilisation Affichage de contenu statique Contenu qui change avec les interactions
Exemples Texte fixe, icônes, images statiques Compteur, formulaire, liste déroulante
Quand utiliser StatelessWidget ?
Utilisez StatelessWidget quand le contenu ne change jamais. Par exemple, un titre de page, une icône fixe, ou un texte qui reste toujours le même.
Quand utiliser StatefulWidget ?
Utilisez StatefulWidget quand le contenu doit changer en fonction des actions de l'utilisateur ou d'autres événements. Par exemple, un compteur qui s'incrémente, un bouton qui change de texte, ou une case à cocher.

🏗️ Structure d'un StatefulWidget

Un StatefulWidget a une structure différente de StatelessWidget. Il est composé de deux classes :

  1. La classe du widget (qui étend StatefulWidget)
  2. La classe de l'état (qui étend State)

Voici la structure de base :

class MonWidget extends StatefulWidget {
  const MonWidget({super.key});

  @override
  State<MonWidget> createState() => _MonWidgetState();
}

class _MonWidgetState extends State<MonWidget> {
  @override
  Widget build(BuildContext context) {
    return Text('Mon contenu');
  }
}

Analysons ce code :

  • class MonWidget extends StatefulWidget : La classe du widget (comme StatelessWidget, mais pour un widget qui peut changer)
  • createState() : Cette méthode crée l'objet qui gère l'état
  • class _MonWidgetState extends State<MonWidget> : La classe qui gère l'état et contient la méthode build()
  • _MonWidgetState : Le préfixe _ signifie que cette classe est privée (utilisée uniquement dans ce fichier)
Pourquoi deux classes ?
Flutter sépare le widget (qui décrit ce qu'il faut afficher) de l'état (qui stocke les données qui peuvent changer). Cette séparation permet à Flutter d'optimiser les performances en ne reconstruisant que ce qui est nécessaire quand l'état change.

🧪 Exemple : changer le texte du bouton lui-même

Voici un exemple concret : un bouton qui change son propre texte quand on clique dessus :

Exemple ElevatedButton change son texte
Un bouton ElevatedButton qui change son texte quand on clique dessus.
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatefulWidget {
  const MaPage({super.key});

  @override
  State<MaPage> createState() => _MaPageState();
}

class _MaPageState extends State<MaPage> {
  String texteBouton = 'Cliquez-moi';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Mon Application'),
        ),
        body: SafeArea(
          child: Center(
            child: ElevatedButton(
              onPressed: () {
                setState(() {
                  texteBouton = 'Déjà cliqué !';
                });
              },
              child: Text(texteBouton),
            ),
          ),
        ),
      ),
    );
  }
}

Analysons ce code étape par étape :

  • class MaPage extends StatefulWidget : Nous utilisons StatefulWidget au lieu de StatelessWidget
  • String texteBouton = 'Cliquez-moi' : Une variable qui stocke le texte du bouton (c'est notre état)
  • child: Text(texteBouton) : Le bouton affiche le texte stocké dans la variable
  • onPressed: () { setState(() { texteBouton = 'Déjà cliqué !'; }); } : Quand on clique, on change le texte avec setState()
Variable d'état :
String texteBouton = 'Cliquez-moi' est une variable qui stocke l'état actuel du texte du bouton. Cette variable est déclarée dans la classe _MaPageState, ce qui permet de la modifier et de mettre à jour l'interface.
Important :
La variable texteBouton doit être déclarée dans la classe _MaPageState (pas dans la méthode build()), car elle doit persister entre les reconstructions du widget.

⚡ Rôle de setState()

setState() est une méthode essentielle dans StatefulWidget. Elle fait deux choses importantes :

  1. Modifie la variable d'état : Change la valeur de la variable (par exemple, texteBouton = 'Déjà cliqué !')
  2. Demande à Flutter de reconstruire l'interface : Indique à Flutter que quelque chose a changé et qu'il doit mettre à jour l'affichage
Pourquoi setState() est nécessaire :
Si vous modifiez simplement la variable sans setState(), la valeur changera en mémoire, mais Flutter ne saura pas qu'il doit mettre à jour l'interface. setState() dit à Flutter : "Hey, quelque chose a changé, reconstruis l'interface !"

Voici la syntaxe de setState() :

setState(() {
  // Ici, vous modifiez les variables d'état
  maVariable = nouvelleValeur;
});
Règle importante :
Vous devez toujours utiliser setState() pour modifier les variables d'état. Si vous modifiez une variable sans setState(), l'interface ne se mettra pas à jour.

🔢 Exemple : bouton modifiant un texte (compteur)

L'exemple classique d'un StatefulWidget est un compteur. Voici un exemple complet :

Exemple de compteur avec StatefulWidget et bouton qui incrémente
Un compteur qui s'incrémente quand on appuie sur le bouton ElevatedButton.
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatefulWidget {
  const MaPage({super.key});

  @override
  State<MaPage> createState() => _MaPageState();
}

class _MaPageState extends State<MaPage> {
  int compteur = 0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Mon Compteur'),
        ),
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  'Compteur : $compteur',
                  style: const TextStyle(
                    fontSize: 32,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 20),
                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      compteur = compteur + 1;
                    });
                  },
                  child: const Text('Incrémenter'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Analysons ce code :

  • int compteur = 0 : Une variable qui stocke le nombre (commence à 0)
  • 'Compteur : $compteur' : Le texte affiche la valeur du compteur. Le $ permet d'insérer la valeur de la variable dans le texte
  • compteur = compteur + 1 : Incrémente le compteur de 1 à chaque clic
  • setState(() { ... }) : Indique à Flutter de mettre à jour l'interface après avoir modifié le compteur
Interpolation de chaînes :
En Dart, vous pouvez insérer la valeur d'une variable dans une chaîne de caractères en utilisant $ :
  • 'Compteur : $compteur' : Affiche "Compteur : 0", puis "Compteur : 1", etc.
  • 'Le nombre est $compteur' : Affiche "Le nombre est 0", puis "Le nombre est 1", etc.
C'est comme faire une concaténation automatique : 'Compteur : ' + compteur.toString(), mais en plus simple.
compteur = compteur + 1 :
Cette ligne prend la valeur actuelle du compteur, ajoute 1, et stocke le résultat dans la variable compteur. Par exemple :
  • Si compteur = 0, alors compteur = 0 + 1 donne compteur = 1
  • Si compteur = 1, alors compteur = 1 + 1 donne compteur = 2
  • Et ainsi de suite...
Vous pouvez aussi écrire compteur++ qui fait exactement la même chose.
Expérimenter :
Essayez de modifier le code :
  • Changez compteur = compteur + 1 en compteur = compteur + 2 pour incrémenter de 2
  • Ajoutez un deuxième bouton qui décrémente : compteur = compteur - 1
  • Changez le texte initial : int compteur = 10 pour commencer à 10

🚫 Un bouton désactivé

Pour désactiver un bouton (le rendre grisé et non cliquable), vous devez passer null au paramètre onPressed. Quand onPressed est null, le bouton devient automatiquement grisé.

ElevatedButton(
  onPressed: null,  // Bouton désactivé
  child: Text('Bouton désactivé'),
)

Vous pouvez aussi désactiver un bouton conditionnellement en utilisant l'opérateur ternaire. Voici un exemple avec notre compteur : le bouton "Incrémenter" est désactivé si le compteur dépasse 10.

ElevatedButton désactivé Flutter
Le bouton ElevatedButton est grisé quand il est désactivé (onPressed: null).
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatefulWidget {
  const MaPage({super.key});

  @override
  State<MaPage> createState() => _MaPageState();
}

class _MaPageState extends State<MaPage> {
  int compteur = 0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Mon Compteur'),
        ),
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  'Compteur : $compteur',
                  style: const TextStyle(
                    fontSize: 32,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 20),
                ElevatedButton(
                  onPressed: compteur < 10 ? () {
                    setState(() {
                      compteur = compteur + 1;
                    });
                  } : null,
                  child: const Text('Incrémenter'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Dans cet exemple, onPressed: compteur < 10 ? () { ... } : null signifie :

  • Si compteur < 10 : Le bouton est actif et incrémente le compteur
  • Si compteur >= 10 : Le bouton est désactivé (grisé) car onPressed est null
Opérateur ternaire :
La syntaxe condition ? valeurSiVrai : valeurSiFaux permet de choisir une valeur selon une condition. Ici, si compteur < 10 est vrai, on retourne la fonction, sinon on retourne null pour désactiver le bouton.

💡 Points clés à retenir

  • StatelessWidget ne peut pas changer après sa création
  • StatefulWidget peut changer dynamiquement grâce à un état
  • L'état est stocké dans des variables déclarées dans la classe State
  • setState() modifie l'état et demande à Flutter de mettre à jour l'interface
  • Vous devez toujours utiliser setState() pour modifier les variables d'état
  • Un StatefulWidget est composé de deux classes : le widget et l'état
  • La méthode build() se trouve dans la classe State
  • Pour désactiver un bouton, passez null au paramètre onPressed
  • Un bouton désactivé devient automatiquement grisé
  • Vous pouvez utiliser l'opérateur ternaire pour activer/désactiver un bouton conditionnellement
Résumé de la structure :
Voici la structure complète d'un StatefulWidget :
class MonWidget extends StatefulWidget {
  const MonWidget({super.key});

  @override
  State<MonWidget> createState() => _MonWidgetState();
}

class _MonWidgetState extends State<MonWidget> {
  // Variables d'état ici
  int maVariable = 0;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        setState(() {
          maVariable = maVariable + 1;
        });
      },
      child: Text('$maVariable'),
    );
  }
}

3.2.3 – Les types de boutons courants en Flutter

Jusqu'à présent, nous avons utilisé ElevatedButton, qui est un type de bouton parmi d'autres. Flutter propose plusieurs types de boutons, chacun ayant un style et une utilisation spécifique. Dans cette section, nous découvrirons les principaux types de boutons et quand les utiliser.

🎯 Pourquoi plusieurs types de boutons

Flutter propose différents types de boutons pour répondre à différents besoins visuels et fonctionnels :

  • Hiérarchie visuelle : Certains boutons sont plus importants que d'autres et doivent se démarquer
  • Contexte d'utilisation : Un bouton dans une barre d'outils n'a pas besoin du même style qu'un bouton principal
  • Design Material : Suivre les guidelines Material Design pour une interface cohérente
  • Flexibilité : Choisir le bon bouton pour chaque situation
Material Design :
Material Design est le système de design de Google. Il définit des règles pour créer des interfaces cohérentes et intuitives. Les différents types de boutons suivent ces règles pour indiquer visuellement leur importance et leur fonction.
Voir la documentation Flutter Material Widgets

🔘 ElevatedButton

ElevatedButton est le bouton principal que nous avons déjà vu. Il a un effet d'élévation (ombre) et est utilisé pour les actions importantes.

ElevatedButton(
  onPressed: () {},
  child: Text('Action principale'),
)
Quand utiliser ElevatedButton :
Utilisez ElevatedButton pour les actions principales de votre application : valider un formulaire, sauvegarder, confirmer une action importante, etc.

🧪 Exemple : ElevatedButton

Voici un exemple simple avec un ElevatedButton :

Illustration ElevatedButton Flutter
ElevatedButton dans une application Flutter.
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatelessWidget {
  const MaPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('ElevatedButton'),
        ),
        body: SafeArea(
          child: Center(
            child: ElevatedButton(
              onPressed: () {
                print('Bouton pressé !');
              },
              child: const Text('Valider'),
            ),
          ),
        ),
      ),
    );
  }
}

📝 TextButton

TextButton est un bouton minimaliste qui ressemble à du texte. Il n'a pas d'ombre ni de bordure, juste du texte avec un effet au survol.

TextButton(
  onPressed: () {},
  child: Text('Action secondaire'),
)
Quand utiliser TextButton :
Utilisez TextButton pour les actions secondaires ou moins importantes : annuler, ignorer, voir plus, etc. Il est moins visible que ElevatedButton, ce qui indique qu'il s'agit d'une action moins prioritaire.

🧪 Exemple : TextButton

Voici un exemple simple avec un TextButton :

Exemple de TextButton Flutter
Exemple d'affichage d'un TextButton dans une application Flutter.
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatelessWidget {
  const MaPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('TextButton'),
        ),
        body: SafeArea(
          child: Center(
            child: TextButton(
              onPressed: () {
                print('Annuler');
              },
              child: const Text('Annuler'),
            ),
          ),
        ),
      ),
    );
  }
}

🔲 OutlinedButton

OutlinedButton est un bouton avec une bordure mais sans fond coloré. Il se situe entre ElevatedButton et TextButton en termes d'importance visuelle.

OutlinedButton(
  onPressed: () {},
  child: Text('Action avec bordure'),
)
Quand utiliser OutlinedButton :
Utilisez OutlinedButton pour des actions importantes mais secondaires par rapport à l'action principale. Par exemple, si vous avez un bouton "Enregistrer" (ElevatedButton), vous pourriez avoir un bouton "Annuler" (OutlinedButton).

🧪 Exemple : OutlinedButton

Voici un exemple simple avec un OutlinedButton :

Exemple OutlinedButton Flutter
Exemple d'OutlinedButton affiché dans une application Flutter.
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatelessWidget {
  const MaPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('OutlinedButton'),
        ),
        body: SafeArea(
          child: Center(
            child: OutlinedButton(
              onPressed: () {
                print('Annuler');
              },
              child: const Text('Annuler'),
            ),
          ),
        ),
      ),
    );
  }
}

🧪 Exemple : comparer les trois types de boutons

Voici un exemple qui montre les trois types de boutons côte à côte :

Comparaison des trois types de boutons Flutter
Les trois types de boutons Flutter affichés verticalement : ElevatedButton, OutlinedButton et TextButton.
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatelessWidget {
  const MaPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Types de boutons'),
        ),
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {},
                  child: const Text('ElevatedButton'),
                ),
                const SizedBox(height: 20),
                OutlinedButton(
                  onPressed: () {},
                  child: const Text('OutlinedButton'),
                ),
                const SizedBox(height: 20),
                TextButton(
                  onPressed: () {},
                  child: const Text('TextButton'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
Hiérarchie visuelle :
Dans une interface, utilisez cette hiérarchie :
  • ElevatedButton : Action la plus importante (1 seul par écran généralement)
  • OutlinedButton : Actions importantes mais secondaires
  • TextButton : Actions moins importantes ou secondaires

🎨 IconButton

IconButton est un bouton qui affiche uniquement une icône, sans texte. Il est plus compact et idéal pour les barres d'outils ou les actions rapides.

IconButton(
  onPressed: () {},
  icon: Icon(Icons.favorite),
)

Analysons ce code :

  • IconButton( : Le widget qui crée un bouton avec une icône
  • icon: Icon(Icons.favorite) : L'icône à afficher (un cœur)
  • onPressed: () {} : L'action à exécuter quand on clique
Quand utiliser IconButton :
Utilisez IconButton pour des actions rapides et fréquentes : favoris, partage, recherche, paramètres, etc. Il prend moins de place qu'un bouton avec texte.

🧪 Exemple : IconButton

Voici un exemple avec plusieurs IconButton :

Exemple IconButton Flutter
Aperçu de plusieurs IconButton côte à côte dans une interface Flutter.
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatelessWidget {
  const MaPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('IconButton'),
        ),
        body: SafeArea(
          child: Center(
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton(
                  onPressed: () {},
                  icon: const Icon(Icons.favorite),
                  color: Colors.red,
                ),
                IconButton(
                  onPressed: () {},
                  icon: const Icon(Icons.share),
                  color: Colors.blue,
                ),
                IconButton(
                  onPressed: () {},
                  icon: const Icon(Icons.settings),
                  color: Colors.grey,
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
Paramètre color :
Vous pouvez personnaliser la couleur de l'icône avec le paramètre color. Par défaut, l'icône utilise la couleur du thème de l'application.

⚡ FloatingActionButton

FloatingActionButton (souvent abrégé FAB) est un bouton circulaire flottant qui apparaît au-dessus du contenu. Il est généralement utilisé pour l'action principale d'un écran.

FloatingActionButton(
  onPressed: () {},
  child: Icon(Icons.add),
)

Pour utiliser un FloatingActionButton, vous devez l'ajouter dans le Scaffold :

Scaffold(
  appBar: AppBar(
    title: Text('Mon Application'),
  ),
  body: Center(
    child: Text('Contenu'),
  ),
  floatingActionButton: FloatingActionButton(
    onPressed: () {},
    child: Icon(Icons.add),
  ),
)
Quand utiliser FloatingActionButton :
Utilisez FloatingActionButton pour l'action principale et la plus fréquente d'un écran. Par exemple : ajouter un nouvel élément, créer un nouveau message, prendre une photo, etc. Il y a généralement un seul FAB par écran.
Position du FAB :
Par défaut, le FloatingActionButton apparaît en bas à droite de l'écran. Vous pouvez changer sa position avec le paramètre floatingActionButtonLocation du Scaffold.

🧪 Exemple : FloatingActionButton

Voici un exemple complet avec un FloatingActionButton :

Exemple FloatingActionButton Flutter
Exemple d'affichage d'un FloatingActionButton dans une application Flutter.
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatelessWidget {
  const MaPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('FloatingActionButton'),
        ),
        body: const Center(
          child: Text(
            'Appuyez sur le bouton + en bas à droite',
            style: TextStyle(fontSize: 18),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            print('FAB pressé !');
          },
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}
Exemple FloatingActionButton.extended Flutter
Exemple d'affichage d'un FloatingActionButton.extended avec texte et icône dans une application Flutter.
FAB avec texte :
Vous pouvez aussi utiliser FloatingActionButton.extended pour un FAB avec texte :
FloatingActionButton.extended(
  onPressed: () {},
  icon: Icon(Icons.add),
  label: Text('Ajouter'),
)

📱 AppBar actions

L'AppBar peut contenir des boutons dans sa zone d'actions (à droite). Ces boutons sont généralement des IconButton pour des actions rapides liées à la page.

AppBar(
  title: Text('Mon Application'),
  actions: [
    IconButton(
      onPressed: () {},
      icon: Icon(Icons.search),
    ),
    IconButton(
      onPressed: () {},
      icon: Icon(Icons.more_vert),
    ),
  ],
)

Analysons ce code :

  • actions: [ ... ] : Une liste de widgets (généralement des IconButton) à afficher à droite de l'AppBar
  • IconButton : Chaque bouton dans la liste d'actions
Quand utiliser AppBar actions :
Utilisez les actions de l'AppBar pour des actions contextuelles à la page : recherche, filtres, paramètres, partage, etc. Ces actions sont accessibles depuis n'importe où sur la page.

🧪 Exemple : AppBar avec actions

Voici un exemple complet avec une AppBar contenant plusieurs actions :

Exemple AppBar avec actions Flutter
Exemple d'AppBar avec plusieurs actions (icônes à droite).
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatelessWidget {
  const MaPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('AppBar avec actions'),
          actions: [
            IconButton(
              onPressed: () {
                print('Recherche');
              },
              icon: const Icon(Icons.search),
            ),
            IconButton(
              onPressed: () {
                print('Favoris');
              },
              icon: const Icon(Icons.favorite),
            ),
            IconButton(
              onPressed: () {
                print('Plus d\'options');
              },
              icon: const Icon(Icons.more_vert),
            ),
          ],
        ),
        body: const Center(
          child: Text(
            'Regardez les icônes dans la barre supérieure',
            style: TextStyle(fontSize: 18),
          ),
        ),
      ),
    );
  }
}
Icônes courantes dans AppBar :
Voici des icônes souvent utilisées dans les actions de l'AppBar :
  • Icons.search : Recherche
  • Icons.favorite : Favoris
  • Icons.share : Partager
  • Icons.settings : Paramètres
  • Icons.more_vert : Menu avec plus d'options (trois points verticaux)
  • Icons.notifications : Notifications

🖼️ Image cliquable avec TextButton

Vous pouvez rendre une image cliquable en l'enveloppant dans un TextButton. C'est utile pour créer des zones cliquables sur des images, comme des bannières publicitaires ou des cartes de produit.

TextButton(
  onPressed: () {
    print('Image cliquée !');
  },
  child: Image.network(
    'https://example.com/image.jpg',
    width: 200,
    height: 200,
  ),
)

Analysons ce code :

  • TextButton( : Le bouton qui rend l'image cliquable
  • onPressed: () { ... } : L'action à exécuter quand on clique sur l'image
  • child: Image.network(...) : L'image affichée dans le bouton
Pourquoi TextButton ?
TextButton est idéal pour rendre une image cliquable car il n'ajoute pas de style visible (pas de bordure, pas de fond). L'image reste telle quelle, mais devient cliquable. Vous pouvez aussi utiliser ElevatedButton ou OutlinedButton si vous voulez un effet visuel au clic.

🧪 Exemple : Image cliquable

Voici un exemple complet avec une image cliquable :

Image cliquable avec TextButton Flutter
Exemple d'image cliquable avec TextButton.
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatelessWidget {
  const MaPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Image cliquable'),
        ),
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                TextButton(
                  onPressed: () {
                    print('Image cliquée !');
                  },
                  child: Image.network(
                    'http://mazoul.online/images/courses/flutter/chapitre_3/owl.jpg',
                    width: 200,
                    height: 200,
                    fit: BoxFit.cover,
                  ),
                ),
                const SizedBox(height: 20),
                const Text(
                  'Cliquez sur l\'image',
                  style: TextStyle(fontSize: 18),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Dans cet exemple, l'image est cliquable grâce au TextButton. Quand vous cliquez sur l'image, le message "Image cliquée !" s'affiche dans la console.

Image locale ou réseau :
Vous pouvez utiliser une image locale avec Image.asset() ou une image réseau avec Image.network(). Les deux fonctionnent avec TextButton.
Astuce :
Pour créer une zone cliquable plus grande autour de l'image, vous pouvez envelopper le TextButton dans un Container avec du padding, ou utiliser un InkWell (nous verrons cela plus tard).

💡 Points clés à retenir

  • Flutter propose plusieurs types de boutons pour différentes situations
  • ElevatedButton : Action principale (avec ombre)
  • OutlinedButton : Action importante mais secondaire (avec bordure)
  • TextButton : Action secondaire (minimaliste, comme du texte)
  • IconButton : Bouton avec uniquement une icône (compact)
  • FloatingActionButton : Bouton circulaire flottant pour l'action principale
  • AppBar actions : Boutons dans la barre supérieure (généralement des IconButton)
  • Choisissez le type de bouton selon l'importance de l'action
Résumé des types de boutons :
Voici un récapitulatif rapide :
Type Style Utilisation
ElevatedButton Fond coloré + ombre Action principale
OutlinedButton Bordure uniquement Action importante secondaire
TextButton Texte simple Action secondaire
IconButton Icône uniquement Action rapide, barre d'outils
FloatingActionButton Cercle flottant Action principale de l'écran

3.2.4 – Widgets d'interaction avancés

Parfois, vous voulez rendre n'importe quel widget cliquable, pas seulement les boutons. Flutter propose deux widgets pour cela : InkWell et GestureDetector.

💧 InkWell

InkWell ajoute un effet de "splash" (ondulation) Material Design quand on clique dessus. C'est idéal pour rendre des widgets Material cliquables.

📝 Syntaxe

Voici comment utiliser InkWell :

// InkWell simple
InkWell(
  onTap: () {
    print('Cliqué !');
  },
  child: Container(
    padding: const EdgeInsets.all(16),
    child: const Text('Cliquez-moi'),
  ),
);

// InkWell avec effet personnalisé
InkWell(
  onTap: () {
    print('Cliqué !');
  },
  splashColor: Colors.blue,
  highlightColor: Colors.blue[100],
  borderRadius: BorderRadius.circular(10),
  child: Container(
    padding: const EdgeInsets.all(16),
    child: const Text('Cliquez-moi'),
  ),
);

Analysons cette syntaxe :

  • onTap : Action à exécuter quand on clique
  • child : Le widget à rendre cliquable
  • splashColor : Couleur de l'effet de splash (optionnel)
  • highlightColor : Couleur de surbrillance au survol (optionnel)
  • borderRadius : Coins arrondis pour l'effet (optionnel)
Effet Material :
InkWell crée un effet visuel Material Design (ondulation) quand on clique. C'est l'effet que vous voyez sur les boutons Material. Pour que l'effet fonctionne correctement, le widget enfant doit être dans un Material ou un Ink widget.

🧪 Exemple : InkWell

Voici un exemple complet qui utilise InkWell :

Exemple InkWell Flutter
L'utilisation de InkWell pour rendre un widget cliquable.
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatelessWidget {
  const MaPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('InkWell'),
      ),
      body: SafeArea(
        child: Center(
          child: Material(
            color: Colors.blue[100],
            borderRadius: BorderRadius.circular(10),
            child: InkWell(
              onTap: () {
                print('Carte cliquée !');
              },
              borderRadius: BorderRadius.circular(10),
              child: Container(
                padding: const EdgeInsets.all(20),
                child: const Text(
                  'Cliquez sur cette carte',
                  style: TextStyle(fontSize: 18),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Dans cet exemple :

  • Un Material entoure l'InkWell pour que l'effet de splash fonctionne
  • L'InkWell rend la carte cliquable
  • Quand on clique, un effet d'ondulation apparaît

👆 GestureDetector

GestureDetector détecte différents types de gestes (tap, double tap, long press, drag, etc.) sans effet visuel Material. C'est plus flexible que InkWell.

📝 Syntaxe

Voici comment utiliser GestureDetector :

// GestureDetector simple (tap)
GestureDetector(
  onTap: () {
    print('Tap !');
  },
  child: Container(
    padding: const EdgeInsets.all(16),
    child: const Text('Cliquez-moi'),
  ),
);

// GestureDetector avec plusieurs gestes
GestureDetector(
  onTap: () {
    print('Tap simple');
  },
  onDoubleTap: () {
    print('Double tap');
  },
  onLongPress: () {
    print('Appui long');
  },
  child: Container(
    padding: const EdgeInsets.all(16),
    child: const Text('Essayez différents gestes'),
  ),
);

Analysons cette syntaxe :

  • onTap : Action pour un tap simple
  • onDoubleTap : Action pour un double tap
  • onLongPress : Action pour un appui long
  • child : Le widget à rendre interactif
Geste :
Un geste est une interaction de l'utilisateur avec l'écran : tap, double tap, appui long, glisser, pincer, etc. GestureDetector peut détecter de nombreux types de gestes différents.

🧪 Exemple : GestureDetector

Voici un exemple complet qui utilise GestureDetector :

Exemple GestureDetector Flutter
L'utilisation de GestureDetector pour détecter des gestes.
import 'package:flutter/material.dart';

void main() {
  runApp(const MaPage());
}

class MaPage extends StatelessWidget {
  const MaPage({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String message = 'Faites un geste';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GestureDetector'),
      ),
      body: SafeArea(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              GestureDetector(
                onTap: () {
                  setState(() {
                    message = 'Tap simple';
                  });
                },
                onDoubleTap: () {
                  setState(() {
                    message = 'Double tap';
                  });
                },
                onLongPress: () {
                  setState(() {
                    message = 'Appui long';
                  });
                },
                child: Container(
                  padding: const EdgeInsets.all(40),
                  decoration: BoxDecoration(
                    color: Colors.blue[100],
                    borderRadius: BorderRadius.circular(10),
                  ),
                  child: const Text(
                    'Zone interactive',
                    style: TextStyle(fontSize: 18),
                  ),
                ),
              ),
              const SizedBox(height: 20),
              Text(
                message,
                style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Dans cet exemple :

  • Un GestureDetector détecte trois types de gestes : tap, double tap, et appui long
  • Chaque geste met à jour le message affiché
  • Le widget enfant est un Container avec un style

🔄 Différences et cas d'utilisation

Caractéristique InkWell GestureDetector
Effet visuel Oui (splash Material) Non
Types de gestes Tap principalement Tap, double tap, long press, drag, etc.
Requis Doit être dans un Material Aucun requis
Style Material Design Neutre
Quand utiliser InkWell :
Utilisez InkWell quand vous voulez :
  • Rendre un widget Material cliquable avec un effet visuel
  • Créer des cartes, des listes, ou des éléments de menu cliquables
  • Avoir un style cohérent avec Material Design
Quand utiliser GestureDetector :
Utilisez GestureDetector quand vous voulez :
  • Détecter plusieurs types de gestes (double tap, appui long, drag...)
  • Rendre n'importe quel widget cliquable sans effet Material
  • Créer des interactions personnalisées
  • Travailler avec des widgets non-Material

💡 Points clés à retenir

  • InkWell ajoute un effet de splash Material Design
  • GestureDetector détecte différents types de gestes sans effet visuel
  • Utilisez InkWell pour des éléments Material avec effet visuel
  • Utilisez GestureDetector pour des gestes avancés ou des widgets non-Material
  • InkWell doit être dans un widget Material pour fonctionner correctement
  • GestureDetector est plus flexible et peut détecter plus de types de gestes

Exercice pratique :
Maintenant que vous connaissez les différents types de boutons, les widgets d'interaction avancés et comment gérer l'état avec StatefulWidget, mettez vos connaissances en pratique avec l'exercice ci-dessous !

🎯 Exercice pratique

Objectif : Reproduire l'interface de portfolio ci-dessous en utilisant tous les concepts appris dans ce chapitre : StatefulWidget, setState(), les différents types de boutons (ElevatedButton, OutlinedButton, TextButton, IconButton, FloatingActionButton), et les widgets AppBar avec actions.

Interface de portfolio à reproduire
Interface à reproduire : Créez cette interface de portfolio en utilisant les widgets et techniques appris dans ce chapitre.