[Flutter] ListView.separated による区切り線表示


目的

  • ListView で行の間に区切り線を表示する
  • リストの先頭と最後にも区切り線を表示する

Androidアプリ / Material Design ではリストの区切り線を表示しないことも多いと思いますが、項目の多いリストでは区切り線を表示することが推奨されています。
https://material.io/components/lists#anatomyVisuals, dividers, and spacing 参照

経緯

  1. ListView に区切り線を表示したい
  2. ListView.separated() が使えそう
  3. リストの先頭と最後に区切り線が表示されない
  4. ググったコードを参考に実装してみる
  5. 先頭と最後の区切り線が二重で表示されているっぽい
  6. 本題

リストの先頭と最後に区切り線が表示されない

サンプルコード

Widget separatedList() {
    final itemCount = 6;
    return ListView.separated(
            padding: EdgeInsets.only(top: 20.0),
            itemBuilder: (context, index) => Container(
                        padding: EdgeInsets.all(20.0),
                        alignment: Alignment.centerLeft,
                        child: Text(
                            'row $index',
                            style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold),
                        ),
                    ),
            separatorBuilder: (context, index) {
                print('separator: $index');
                return Divider(height: 0.5);
            },
            itemCount: itemCount);
}

結果

ログ

I/flutter (13762): separator: 0
I/flutter (13762): separator: 1
I/flutter (13762): separator: 2
I/flutter (13762): separator: 3
I/flutter (13762): separator: 4

n 行の表示に対して、 (n-1) 回 separatorBuilder が呼ばれていることがわかります。
表示されている区切り線の本数と一致しているので、 ListView.separated としては期待通りの表示であることがわかります。

ググったコード

雑に検索してパッと次のサイト(コード)を見つけました。
- Flutter - How to add first top and last bottom list with divider() on ListView.separated()
- [Flutter] ListView.separated() で、一番最後にDivider()を表示する

itemCount をプラスして itemBuilderDivider() を返すという内容です。

参考にして実装してみたらそれらしい表示になりましたが、先頭と最後の区切り線に違和感があり separatorBuilder のログを拾ってみました。

サンプルコード(修正1)

Widget separatedList() {
    final itemCount = 6;
    return ListView.separated(
            padding: EdgeInsets.only(top: 20.0),
            itemBuilder: (context, index) {
                if (index == 0 || index == itemCount + 1) {
                    return Divider(height: 0.5);
                }
                return Container(
                    padding: EdgeInsets.all(20.0),
                    alignment: Alignment.centerLeft,
                    child: Text(
                        'row ${index - 1}',
                        style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold),
                    ),
                );
            },
            separatorBuilder: (context, index) {
                print('separator: $index');
                return Divider(height: 0.5);
            },
            itemCount: itemCount + 2);
}

結果(修正1)

ログ(修正1)

I/flutter (13762): separator: 0
I/flutter (13762): separator: 1
I/flutter (13762): separator: 2
I/flutter (13762): separator: 3
I/flutter (13762): separator: 4
I/flutter (13762): separator: 5
I/flutter (13762): separator: 6

separatorBuilder が7回呼ばれているから問題なし!
かと思ったら

if (index == 0 || index == itemCount + 1) {
    return Divider(height: 0.5);
}

により2本の区切り線を描画しているはずなので、合計で9本となりやはり余分に線を描画していました。
itemCount を + 2 して return Divider(height: 0.5) している分も行として扱われるため、これに対してもさらに区切り線を描画しようとするためですね。

対策

先頭と最後で Divider(height: 0.5) を返す代わりに空の Container() を返すように変更しました。

サンプルコード(修正2)

Widget separatedList() {
    final itemCount = 6;
    return ListView.separated(
            padding: EdgeInsets.only(top: 20.0),
            itemBuilder: (context, index) {
                if (index == 0 || index == itemCount + 1) {
                    return Container();
                }
                return Container(
                    padding: EdgeInsets.all(20.0),
                    alignment: Alignment.centerLeft,
                    child: Text(
                        'row ${index - 1}',
                        style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold),
                    ),
                );
            },
            separatorBuilder: (context, index) {
                print('separator: $index');
                return Divider(height: 0.5);
            },
            itemCount: itemCount + 2);
}

結果(修正2)

期待した表示となりました。
リストの先頭と最後には区切り線を表示しないデザインとするのが良さそうですね。