🐈

SliverListでもListView.separatedと同様の機能をシュッと実現する

2022/09/06に公開

早速ですが、Sliver、便利ですよね。皆さんは活用していますか?

ふと自分が個人で開発をしているアプリの少し古めの実装を見直したところ、SingleChildScrollView, Column, ListView(shrinkWrap: true)などを使ってリスト以外にもいくつかWidgetを表示する実装をしていたのですが、これは非常に良くない実装だったので、意を決してCustomScrollView + Sliverで書き直しをしました。

その際に、ListView.separatedで実装されたリストをSliverListに移行する必要があったのですが、 SliverListで用いる SliverChildBuilderDelegateにはセパレーターを設定するようなプロパティが用意されておらず、もし要素の間にセパレーターを挟みたい場合は、少し実装を工夫する必要があります。

なので、同様の書き方ができるように以下のようにextensionを用意しました。

import 'dart:math' as math;
import 'package:flutter/material.dart';

extension SliverListEx on SliverList {
  static SliverList separated({
    required int itemCount,
    required NullableIndexedWidgetBuilder itemBuilder,
    required NullableIndexedWidgetBuilder separatorBuilder,
  }) {
    return SliverList(
      delegate: SliverChildBuilderDelegate(
        (context, index) {
          final itemIndex = index ~/ 2;
          return index.isEven 
	      ? itemBuilder(context, itemIndex)
	      : separatorBuilder(context, itemIndex);
        },
        childCount: math.max(0, itemCount * 2 - 1),
      ),
    );
  }
}

ポイントとしては、受け取った要素数*2 - 1のリストを作成するようにし、偶数番号のindexの時はitemBuilderを、奇数indexの場合はseparatorBuilderを呼び出してリストアイテムとなるWidgetを返すようにしている点です。
これらの処理を包んでおけば、複数の箇所でSliverを使いつつも、ListView.separated同様のことをしたい時に役に立ちますね。

簡単に、使用例を示しておきます。

class Page extends StatelessWidget {
  const Page({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Page'),
      ),
      body: CustomScrollView(
        slivers: [
	  Banner(),
	  StickyHeader(),
          SliverListEx.separated(
            itemCount: 100,
            itemBuilder: (_, index) => ListTile(
              key: ValueKey(index),
              title: Text('item: $index'),
            ),
            separatorBuilder: (_, __) => const Divider(
              color: Colors.blueGrey,
            ),
          )
        ],
      ),
    );
  }
}

実際の動作は、以下のDartPadで確認できます。

https://dartpad.dev/?id=0c454f404aee708eaffc0f124cf7f233

(余談)

flutter/flutterのリポジトリで過去issueが上がっていた形跡があります。
SliverChildBuilderDelegateListViewと比べて低レベルAPIである、それにseparatorのサポートを追加するのを推奨したい気持ちはあるものの、APIインターフェースを拡大すると、メンテナンスしていかないと行けないため、シンプルな状態を保ちたい、といったお気持ちが表明されていて、現在はissueがクローズされています(意訳)

https://github.com/flutter/flutter/issues/48543

参考

https://api.flutter.dev/flutter/widgets/SliverList-class.html

https://www.youtube.com/watch?v=ORiTTaVY6mM

Discussion