🧩

【Flutter】SliverListとかSliverGridとかのSliverの意味わかってる?

2023/12/07に公開

はい、わかってませんでした。
でも最近何となく理解しました。
Sliver という単語を調べたら、小さくて細い何かの破片、というような意味でした。

https://docs.flutter.dev/ui/layout/scrolling/slivers

A sliver is a portion of a scrollable area

公式リファレンスの冒頭にも書いてあります。なるほど。
スクロール可能なエリアの一部分なんですね。だからパズルのピースのように Sliver〜 のWidgetを複数配置して、一つのスクロールウィジェットを作れる、ということと理解しました。

これを使うメリットは、パフォーマンスです。
例えば以下のような画面で、Bがページング読み込みするGridViewだとします。

以下のような実装だとメモリ負荷が高くなって落ちる場合があります。というか大量にアイテムを読み込むと、いつか落ちる。自分の作っているアプリで実際に起きた事象です。
(ちょっと古いですが Flutter 3.7.10 のiOSのみで再現確認。Androidは再現せず)

class ExamplePage extends HookConsumerWidget {
  const ExamplePage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    // ...
    return SingleChildScrollView(
      controller: scrollController, // スクロール末尾に到達したらアイテムを追加で読み込むような実装
      child: Column(
        children: [
          // いろいろなWidget(Aの領域)
          const SizedBox.square(),
	  const SizedBox.square(),
          const SizedBox.square(),
          // ページングで読み込むGridView(Bの領域)
          GridView.builder(
            itemCount: itemCount.value + (isLoading.value ? 1 : 0),
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3,
              crossAxisSpacing: 4,
              mainAxisSpacing: 4,
              childAspectRatio: 0.6,
            ),
            itemBuilder: (context, index) {
	      // GridViewのアイテム
              return const SizedBox.square();
            },
          ),
        ],
      ),
    );
  }
}

しかし、これをSliverを使うと、表示領域とその前後しか生成されないないため、メモリにやさしい。落ちない。

class ExamplePageAfter extends HookConsumerWidget {
  const ExamplePageAfter({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    // ...
    return CustomScrollView(
      controller: scrollController,
      slivers: [
        // いろいろなWidget(Aの領域)
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) {
              return const SizedBox.square();
            },
            childCount: 3,
          ),
        ),
        // ページングで読み込むGridView(Bの領域)
        SliverGrid(
          gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            crossAxisSpacing: 4,
            mainAxisSpacing: 4,
            childAspectRatio: 0.6,
          ),
          delegate: SliverChildBuilderDelegate(
            (context, index) {
	      // GridViewのアイテム
              return const SizedBox.square();
            },
            childCount: itemCount.value + (isLoading.value ? 1 : 0),
          ),
        )
      ],
    );
  }
}

Discussion