Flutter ListView 使い方を覚えるなら、まずはListViewとListView.builderの違いを理解するのがおすすめです。少ない固定データならListView、件数が多いデータならListView.builderを使い分けると、読みやすく効率的な画面を作れます。この記事では、3件のメニュー表示からデータ連動の一覧まで順番に説明します。
初心者の疑問: ListViewは、画面をスクロールさせるためだけのウィジェットですか? 答え: スクロールに加えて、縦または横方向へウィジェットを並べる役割も持っています。
ListViewとは
ListViewは、子ウィジェットを一方向に並べるスクロール可能なウィジェットです。Flutter公式APIでは、標準のListView、遅延生成するListView.builder、区切り線を作りやすいListView.separatedなどのコンストラクターが用意されています。
たとえば、メニューや設定項目のように、同じ画面に複数の項目を縦に並べたいときに向いています。
最初に作る固定の一覧
項目数が少なく、表示内容が決まっているなら、childrenにウィジェットを直接書く方法が分かりやすいです。ListTileを使うと、アイコンとタイトルを持つ行を簡単に作れます。
import 'package:flutter/material.dart';
class BasicListPage extends StatelessWidget {
const BasicListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('メニュー')),
body: ListView(
children: const [
ListTile(
leading: Icon(Icons.map),
title: Text('地図'),
),
ListTile(
leading: Icon(Icons.photo_album),
title: Text('アルバム'),
),
ListTile(
leading: Icon(Icons.phone),
title: Text('電話'),
),
],
),
);
}
}
ListViewは内容が画面の高さを超えるとスクロールできます。Scaffoldのbodyに置く形は、最初の動作確認にも適しています。
ポイント: まずは3〜5項目の固定リストで、スクロールと行の見た目を確認してからデータ連動へ進むと、エラーの場所を特定しやすくなります。
データから一覧を作るListView.builder
実際のアプリでは、商品、メッセージ、検索結果などをList<String>やモデルのリストから表示することが多くなります。その場合はListView.builderを使います。
class ProductListPage extends StatelessWidget {
ProductListPage({super.key});
final List<String> products = const [
'ノートパソコン',
'ワイヤレスマウス',
'キーボード',
'USBハブ',
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('商品一覧')),
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text(products[index]),
);
},
),
);
}
}
itemCountは表示する件数、indexは0から始まる項目番号です。products[index]で、現在の行に対応するデータを取り出しています。
Flutter公式APIによると、ListView.builderは表示が必要な子を必要に応じて作るため、項目数が多い一覧や、件数が増える一覧に適しています。itemCountを指定しておくと、スクロールバーの大きさや終了位置も正しく扱いやすくなります。
初心者の疑問: indexは1から始まりますか? いいえ、Dartのリストと同じく0から始まります。画面上の番号を1から表示したいときだけ、例のようにindex + 1を使います。
ListViewとListView.builderの使い分け
| 作りたい一覧 | 向いている方法 | 理由 |
|---|---|---|
| 項目が数個で固定 | ListView | コードを直接読めて簡単 |
| 配列のデータを表示 | ListView.builder | indexで繰り返し表示できる |
| 件数が多い、増える可能性がある | ListView.builder | 子ウィジェットを必要な分だけ生成しやすい |
| 行と行の間に区切りを入れる | ListView.separated | 区切り用の処理を分けて書ける |
比較ポイント: 「今は3件だからListView」ではなく、今後APIやデータベースから件数が増えるかも判断材料にしましょう。増えるなら、最初からListView.builderにしておくと作り直しを減らせます。
区切り線を入れるListView.separated
行の間にDividerなどを入れたいときは、ListView.separatedが便利です。項目を作るitemBuilderと、区切りを作るseparatorBuilderを分けられます。
ListView.separated(
itemCount: products.length,
itemBuilder: (context, index) {
return ListTile(title: Text(products[index]));
},
separatorBuilder: (context, index) {
return const Divider(height: 1);
},
)
最後の項目の後ろに区切り線を追加したくない場合も、separatorBuilderなら「項目の間」だけに表示できます。
横向きにスクロールする
初期状態のscrollDirectionは縦方向です。カードを横に並べたいときはAxis.horizontalを指定します。
ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: products.length,
itemBuilder: (context, index) {
return SizedBox(
width: 160,
child: Card(
child: Center(child: Text(products[index])),
),
);
},
)
横向きでは、子ウィジェットに幅を指定しないとレイアウトが分かりにくくなります。SizedBoxやConstrainedBoxでカードのサイズを決めておくと安定します。
初心者がつまずきやすいエラー
`RangeError`が出る
itemCountと、実際のリストの長さが合っていない可能性があります。たとえばitemCount: 10なのに、データが3件しかない状態でproducts[index]を呼ぶと、存在しない番号を参照してしまいます。
基本は次のように、同じリストのlengthを使います。
itemCount: products.length
`ListView`が表示されない、または高さのエラーになる
Columnの中にスクロールビューを置くとき、縦方向の高さが決まらないことがあります。画面の残りを使いたいならExpandedで包みます。
Column(
children: [
const Text('商品一覧'),
Expanded(
child: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) =>
ListTile(title: Text(products[index])),
),
),
],
)
`shrinkWrap: true`をどこでも使う
shrinkWrapは、内容に合わせてスクロールビューの大きさを決めたいときに使います。ただし、長い一覧を別のスクロールビューの中に入れる構成では、レイアウトや処理の負担が増えやすくなります。
注意: ListViewをSingleChildScrollViewの中に重ねる前に、画面全体を1つのListViewで表せないか検討しましょう。スクロール方向が重複すると、操作しにくい画面になりやすいです。
空の一覧も先に考える
データが0件のときに、空白の画面だけを表示すると、読み手は「読み込みに失敗したのか」と迷います。データがある場合とない場合で表示を切り替えます。
Widget buildBody(List<String> products) {
if (products.isEmpty) {
return const Center(child: Text('表示できる商品がありません'));
}
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) =>
ListTile(title: Text(products[index])),
);
}
この考え方は、API通信中のローディング表示や、通信失敗時の再試行ボタンにも広げられます。
チェックリスト: itemCountがリストの長さと一致している/0件の表示を用意した/縦横の向きを決めた/親の高さが決まっている/長い一覧ではbuilderを使っている。
まとめ:まずはbuilderで実用的な一覧を作る
Flutter ListViewの使い方で大切なのは、表示するデータの性質に合わせてコンストラクターを選ぶことです。
- 固定された少数の項目は
ListViewで直接並べる - 配列やAPIのデータは
ListView.builderで生成する - 区切り線が必要なら
ListView.separatedを使う itemCountには元データのlengthを指定する- 空データ、親の高さ、
shrinkWrapの使い方を確認する
次にやること: まずはList<String>を4件用意し、ListView.builderで表示してみましょう。次にListTileのタップ処理を追加すると、一覧から詳細画面へ進むアプリの形に近づきます。
コメント