Kodokon kodokon.com

ListView.builderによる高性能なリスト

ListView.builder、ListTile、そして独自のウィジェットに切り出した項目のおかげで、数千個の項目のリストを遅延なく表示しましょう。

8 分 · 3 問

このレッスンを Kodokon で開く

実際のアプリはほとんどの場合リストを表示します。メッセージ、商品、連絡先などです。すでに知っているListView(children: ...)コンストラクターは、その子ウィジェットをすべて一度に構築します。10個の項目なら問題ありませんが、1000個となると悲惨です。ListView.builderは、スクロールに応じて必要なときに、表示領域の近くにある項目だけを構築します。

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]),
          );
        },
      ),
    );
  }
}
10,000個の名前があっても、表示されている項目だけが構築される

二つのパラメーターがこの仕組みのすべてを担います。itemCountは項目の総数を宣言し、itemBuilderは指定されたインデックスの項目を、それが画面に近づいたときだけ生成します。各行の中身については、ListTileがあなたのMaterialの味方です。leadingtitlesubtitletrailingが行を構造化し、onTapがタッチ時のインクの波紋を提供します。しかし項目が大きくなってきたら、itemBuilderの中で膨れ上がらせてはいけません。独自のウィジェットに切り出しましょう。

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,
    );
  }
}
再利用可能でテスト可能なウィジェットになったリスト項目

切り出した項目は、単独でテストでき、他の画面でも再利用でき、itemBuilderをごくシンプルにします。よくある要望がもう一つ残っています。区切り線です。ListView.separatedbuilderのように動きますが、三つ目のパラメーターseparatorBuilderがあり、各項目の間で呼び出されます。最後の要素のあとに区切り線が置かれることは決してありません。

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は各項目の間にDividerを挿入する

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. 500個の項目のリストに対して、なぜListView(children: ...)よりListView.builderを選ぶのですか?
    • 項目を自動的に並べ替える
    • 表示されている項目だけを、必要に応じて構築する
    • 画像をキャッシュする
    • 区切り線を自動的に追加する
  2. 各項目の間にウィジェットを挿入するコンストラクターはどれですか?
    • ListView.divided
    • ListView.builder
    • ListView.separated
  3. リスト項目を独自のウィジェットクラスに切り出す利点は何ですか?
    • ListView.builderがコンパイルされるために必須である
    • constを可能にし、再構築を切り離し、再利用を容易にする
    • リストをスクロール可能にする
    • itemCountを書く手間を省く