Kodokon kodokon.com

Listes performantes avec ListView.builder

Affichez des listes de milliers d'éléments sans ralentissement grâce à ListView.builder, ListTile et des items extraits en widgets.

8 min · 3 questions

Ouvrir cette leçon dans Kodokon

Une application réelle affiche presque toujours des listes : messages, produits, contacts. Le constructeur ListView(children: ...) que vous connaissez construit tous ses enfants d'un coup - acceptable pour dix éléments, catastrophique pour mille. ListView.builder ne construit que les items proches de la zone visible, à la demande, au fil du défilement.

DART
import 'package:flutter/material.dart';

class ContactsScreen extends StatelessWidget {
  const ContactsScreen({super.key, required this.names});

  final List<String> names;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Contacts')),
      body: ListView.builder(
        itemCount: names.length,
        itemBuilder: (context, index) {
          return ListTile(
            leading: const Icon(Icons.person),
            title: Text(names[index]),
          );
        },
      ),
    );
  }
}
Seuls les items visibles sont construits, même avec 10 000 noms.

Deux paramètres portent tout le mécanisme : itemCount annonce le nombre total d'éléments et itemBuilder fabrique l'item d'un index donné, uniquement quand il approche de l'écran. Pour le contenu de chaque ligne, ListTile est l'allié Material : leading, title, subtitle et trailing structurent la ligne, onTap fournit l'effet d'encre au toucher. Mais dès que l'item grossit, ne le laissez pas gonfler dans itemBuilder : extrayez-le dans son propre widget.

DART
import 'package:flutter/material.dart';

class ContactTile extends StatelessWidget {
  const ContactTile({
    super.key,
    required this.name,
    required this.onTap,
  });

  final String name;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      leading: CircleAvatar(child: Text(name[0])),
      title: Text(name),
      trailing: const Icon(Icons.chevron_right),
      onTap: onTap,
    );
  }
}
Un item de liste devenu un widget réutilisable et testable.

Un item extrait se teste isolément, se réutilise dans d'autres écrans et rend l'itemBuilder trivial. Reste un besoin fréquent : les séparateurs. ListView.separated fonctionne comme builder avec un troisième paramètre, separatorBuilder, appelé entre chaque paire d'items - le dernier élément n'est jamais suivi d'un séparateur.

DART
import 'package:flutter/material.dart';

class ContactList extends StatelessWidget {
  const ContactList({super.key, required this.names});

  final List<String> names;

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      itemCount: names.length,
      separatorBuilder: (context, index) {
        return const Divider(height: 1);
      },
      itemBuilder: (context, index) {
        return ContactTile(
          name: names[index],
          onTap: () => debugPrint(names[index]),
        );
      },
    );
  }
}
separatorBuilder insère un Divider entre chaque paire d'items.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Pourquoi préférer ListView.builder à ListView(children: ...) pour une liste de 500 éléments ?
    • Il trie automatiquement les éléments
    • Il ne construit que les items visibles, à la demande
    • Il met les images en cache
    • Il ajoute automatiquement des séparateurs
  2. Quel constructeur insère un widget entre chaque paire d'items ?
    • ListView.divided
    • ListView.builder
    • ListView.separated
  3. Quel est l'intérêt d'extraire un item de liste dans sa propre classe de widget ?
    • C'est obligatoire pour que ListView.builder compile
    • Cela permet const, isole les reconstructions et facilite la réutilisation
    • Cela rend la liste défilable
    • Cela évite d'écrire itemCount