SliverListでもListView.separatedと同様の機能をシュッと実現する
早速ですが、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で確認できます。
(余談)
flutter/flutterのリポジトリで過去issueが上がっていた形跡があります。
SliverChildBuilderDelegate
はListView
と比べて低レベルAPIである、それにseparator
のサポートを追加するのを推奨したい気持ちはあるものの、APIインターフェースを拡大すると、メンテナンスしていかないと行けないため、シンプルな状態を保ちたい、といったお気持ちが表明されていて、現在はissueがクローズされています(意訳)
参考
Discussion