Créer un menu déroulant dans Flutter avec ExpansionTile


Avatar de Pierre Courtois

Le widget ExpansionTile vous permet de construire des menus déroulant dans votre application Flutter, de manière très. Dans ce guide, je vous explique comment le mettre en place.


Qu’est-ce que le widget ExpansionTile et pourquoi l’utiliser ?

Le widget ExpansionTile est un widget de Flutter utilisé pour créer des listes pouvant s’ouvrir et se refermer de manière fluide. Vous allez pouvoir l’utiliser pour présenter de grandes quantités de contenu dans une interface compacte. Un ExpansionTile est particulièrement utile pour afficher des sous-catégories d’éléments ou des informations supplémentaires de manière conditionnelle, ce qui améliore l’expérience utilisateur en offrant une navigation plus intuitive.

Voici quelques exemples d’utilisation du widget ExpansionTile dans une application mobile :

  • FAQ dans une application : chaque question peut être un ExpansionTile, qui dévoile la réponse en dessous.
  • Menu avec sous-catégories : un menu de navigation peut utiliser ExpansionTile pour afficher les catégories principales et les sous-catégories.
  • Fiches produits ou profils utilisateur : afficher des informations supplémentaires telles que les spécifications d’un produit ou des détails d’un profil.

Comment mettre en place un widget ExpansionTile ?

La mise en place du widget ExpansionTile est assez simple pour des listes fixes, mais peut s’avérer un peu plus complexe quand vous souhaitez construire des listes dynamiques. Je vous montre ici les deux manières de faire.

Création d’une ExpansionTile avec des ListTiles créés manuellement

Voici un exemple de code où chaque ExpansionTile contient plusieurs ListTile ajoutés manuellement. Ce type de mise en place est idéal pour des listes statiques ou de petite taille.

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("Exemple d'ExpansionTile")),
        body: ListView(
          children: <Widget>[
            ExpansionTile(
              title: Text("Catégorie 1"),
              children: <Widget>[
                ListTile(
                  title: Text("Sous-élément 1"),
                  onTap: () {
                    // Action lors du clic sur cet élément
                  },
                ),
                ListTile(
                  title: Text("Sous-élément 2"),
                  onTap: () {
                    // Action lors du clic sur cet élément
                  },
                ),
              ],
            ),
            ExpansionTile(
              title: Text("Catégorie 2"),
              children: <Widget>[
                ListTile(
                  title: Text("Sous-élément 1"),
                  onTap: () {
                    // Action lors du clic sur cet élément
                  },
                ),
                ListTile(
                  title: Text("Sous-élément 2"),
                  onTap: () {
                    // Action lors du clic sur cet élément
                  },
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Dans cet exemple, chaque ExpansionTile représente une catégorie, et chaque ListTile représente un sous-élément appartenant à cette catégorie. L’utilisateur peut cliquer sur un titre de catégorie pour l’ouvrir et afficher les sous-éléments.

Création d’une ExpansionTile avec des ListTiles générés via StreamBuilder et Firebase

Une manière dynamique de générer des ListTiles dans un ExpansionTile est d’utiliser un StreamBuilder. Cela permet de récupérer des données en temps réel depuis Firebase et d’afficher les éléments de manière dynamique. Vous avez ici deux options :

  • Générer la liste à partir d’une collection de documents ;
  • La générer à partir d’un seul document, en récupérant un champ de type List.

Création d’un menu dynamique à partir d’une collection de documents Firebase

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:tutoflutter/firebase_options.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("ExpansionTile avec Firebase")),
        body: FirebaseExpansionTile(),
      ),
    );
  }
}

class FirebaseExpansionTile extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: FirebaseFirestore.instance.collection('Expansiontile').snapshots(),
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (!snapshot.hasData) {
          return Center(child: CircularProgressIndicator());
        }

        return ListView(
          children: snapshot.data!.docs.map((DocumentSnapshot document) {
            return ExpansionTile(
              title: Text(document['title']),
              children: (document['items'] as List).map((item) {
                return ListTile(
                  title: Text(item),
                  onTap: () {
                    // Action lors du clic sur cet élément
                  },
                );
              }).toList(),
            );
          }).toList(),
        );
      },
    );
  }
}

Ici, je construis un ListTile pour chacun des documents contenus dans ma collection ‘Expansiontile’. Puis, j’utilise les champs title et items (ma liste d’items), pour construire chaque ListTile. Vous pouvez ensuite adapter ce code selon vos besoins, ou limiter le streambuilder à un seul document.

Création d’un menu dynamique à partir d’une collection de documents Firebase

Une deuxième manière de créer un ExpansionTile dynamique est de récupérer une liste à partir d’un seul document dans Firebase. Dans ce cas, vous avez besoin d’adapter très légèrement votre Streambuilder, comme suit :

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:tutoflutter/firebase_options.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text("ExpansionTile avec Firebase")),
        body: FirebaseSingleDocumentExpansionTile(),
      ),
    );
  }
}


class FirebaseSingleDocumentExpansionTile extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: FirebaseFirestore.instance.collection('Expansiontile').doc('test1').snapshots(),
      builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
        if (!snapshot.hasData) {
          return Center(child: CircularProgressIndicator());
        }

        var document = snapshot.data!;
        var title = document['title'];
        var items = document['items'] as List;

        return ListView(
          children: [
            ExpansionTile(
              title: Text(title),
              children: items.map((item) {
                return ListTile(
                  title: Text(item),
                  onTap: () {
                    // Action lors du clic sur cet élément
                  },
                );
              }).toList(),
            ),
          ],
        );
      },
    );
  }
}

Ici, je viens récupérer mes champs dans mon document test1 et les utilise pour construire mon menu et mes sous catégories.

Personnalisation du widget ExpansionTile

Comme la plupart des widgets, vous pouvez personnaliser votre ExpansionTile pour l’adapter à vos besoins. Par exemple; il vous est possible de changer l’icône de votre liste en utilisant les propriétés trailing ainsi qu’iconColor.

La couleur quant à elle, pourra être gérée, via les propriétés backgroundColor, collapsedBackgroundColor, et textColor.

Enfin, il vous est possible de définir les marges et les paddings des éléments de votre liste, grâce aux propriétés tilePadding et childrenPadding.

Avatar de Pierre Courtois