📘

【Flutter Widget of the Week #12】SliverList & SliverGridを使ってみた

2022/10/15に公開

はじめに

Flutter Widget of the Week #12 SliverList & SliverGrid についてまとめましたので、紹介します。
https://youtu.be/ORiTTaVY6mM

SliverList & SliverGrid とは

ListView や GridView は List や Grid を別々にスクロールさせたいときは最適です。
ListView や GridView
List や Grid の中身はスクロールできるが List や Grid 自体のスクロールはできない
しかし、List と Grid をまとめてスクロールしたり他の複雑なスクロール効果を作成したりしたい場合は今回紹介する SliverList と SliverGrid を使うのが効果的です。
SliverList や SliverGrid
List と Grid を一緒にスクロールできる
Sliver はスクロールできる領域の一部分として扱うことができ、CustomScrollViewのひとつの要素になります。
なので、List と Grid の組み合わせだけでなく、 List と List、 Grid と Gridのようにアイテムの種類は同じだけど、入れる内容が異なる Silver を集めても、スクロールできる一つの領域として扱うことができる便利な Widget なのです。
次に SliverList と SliverGrid それぞれについて説明します。

SliverList

CustomScrollView で垂直方向にスクロールできるアイテムのリストを表示できます。

(new) SliverList SliverList(
  {
    Key? key,
    required SliverChildDelegate delegate
  }
)

SliverList は delegate パラメータを取ります。
delegete パラメータは、スクロールして次のアイテムが表示されそうになったときにそのアイテムをビルドするための関数を設定します。
delegeteパラメータには、SliverChildListDelegate か SliverChildBuilderDelegate が使われています。

SliverChildListDelegate について

SliverChildListDelegate は以下のように実際の children のリストを指定したいときに使います。

SliverList(
  delegate: SliverChildListDelegate(
    [
      Container1(),  //widget
      Container2(),  //anotherWidget
      Container3().  //yetAnotherWidget
      ・・・            // children 内に入れた Widgte の分だけリストとして表示される
    ],
  ),
),

SliverChildBuilderDelegate について

SliverChildBuilderDelegate は膨大な数のアイテムを持つリストに適していて、アイテムをビルドする関数を指定して List を作ります。

SliverList(
  delegate: SliverChildBuilderDelegate(
    (BuildContext context, int index) {
      return Container();  //widget・・・ここで指定した Widget が複数作られる
    },
  ),
);

SliverList サンプルコード

main.dart
class SliverListSample extends StatefulWidget {
  const SliverListSample({super.key});

  
  State<SliverListSample> createState() => _SliverListSampleState();
}

class _SliverListSampleState extends State<SliverListSample> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          const SliverAppBar(
            backgroundColor: Colors.green,
            title: Text('Silver List'),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                return Card(
                  margin: const EdgeInsets.all(15),
                  child: Container(
                    color: Colors.orange[100 * (index % 12 + 1)],
                    height: 60,
                    alignment: Alignment.center,
                    child: Text(
                      "List Item $index",
                      style: const TextStyle(fontSize: 30),
                    ),
                  ),
                );
              },
              childCount: 10,
            ),
          ),
        ],
      ),
    );
  }
}

SliverGrid

CustomScrollView で水平方向にスクロールできるアイテムのリストを表示できます。

(new) SliverGrid SliverGrid({
  Key? key,
  required SliverChildDelegate delegate,
  required SliverGridDelegate gridDelegate,
})

SliverGrid には delegate パラメータと gridDelegate パラメータがあります。
delegate パラメータは SliverList で説明したのと同じです。
gridDelegate パラメータは Grid のサイズと位置を設定するパラメータで、
SliverGridDelegateWithFixedCrossAxisCountSliverGridDelegateWithMaxCrossAxisExtent などが使われます。

SliverGrid には他にも SliverGrid.count と SliverGrid.extent が使えます。

SliverGrid.count について

SliverGrid.count はリスト内のアイテムが水平方向にスクロールでき、かつ項目数を固定する場合に使用されます。
SliverGrid.count のコンストラクタは以下のようになってます。

(new) SliverGrid SliverGrid.count({
  Key? key,
  required int crossAxisCount,  // 列の数
  double mainAxisSpacing = 0.0,  // アイテムとアイテムの縦の隙間の幅
  double crossAxisSpacing = 0.0,  // アイテムとアイテムの横の隙間の幅
  double childAspectRatio = 1.0,  // アスペクト比 「横幅/高さ」
  List<Widget> children = const <Widget>[],
})

SliverGrid.extent について

SliverGrid.extent はリスト内のアイテムが水平方向にスクロールでき、かつアイテムの数が既知の場合に使用されます。
SliverGrid.extent のコンストラクタは以下のようになってます。

(new) SliverGrid SliverGrid.extent({
  Key? key,
  required double maxCrossAxisExtent, // 一つのアイテムの横幅
  double mainAxisSpacing = 0.0, // アイテムとアイテムの縦の隙間の幅
  double crossAxisSpacing = 0.0,  // アイテムとアイテムの横の隙間の幅
  double childAspectRatio = 1.0, // アスペクト比 「横幅/高さ」
  List<Widget> children = const <Widget>[],
})

SliverGrid サンプルコード

main.dart
class SliverGridSample extends StatefulWidget {
  const SliverGridSample({super.key});

  
  State<SliverGridSample> createState() => _SliverGridSampleState();
}

class _SliverGridSampleState extends State<SliverGridSample> {
  final String url =
      'https://images.unsplash.com/photo-1664840504239-991007ba2393?crop=entropy&cs=tinysrgb&fm=jpg&ixid=MnwzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE2NjU4MTgyMzk&ixlib=rb-1.2.1&q=80';

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          const SliverAppBar(
            floating: false,
            expandedHeight: 120.0,
            flexibleSpace: FlexibleSpaceBar(
              title: Text('SilverGrid Sample'),
            ),
          ),
          SliverGrid(
            gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
              maxCrossAxisExtent: 200.0,
              mainAxisSpacing: 10.0,
              crossAxisSpacing: 10.0,
              childAspectRatio: 4.0,
            ),
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                return Container(
                  child: Image(
                    image: NetworkImage(url),
                    fit: BoxFit.fitWidth,
                  ),
                );
              },
              childCount: 50,
            ),
          ),
          SliverToBoxAdapter(
            child: Container(
              color: Colors.yellow,
              padding: const EdgeInsets.all(8.0),
              child: const Text('SliverToBoxAdapter',
                  style: TextStyle(fontSize: 24)),
            ),
          ),
          SliverGrid.count(
            crossAxisCount: 3,
            mainAxisSpacing: 10.0,
            crossAxisSpacing: 10.0,
            childAspectRatio: 4.0,
            children: <Widget>[
              Container(color: Colors.red),
              Container(color: Colors.green),
              Container(color: Colors.blue),
              Container(color: Colors.red),
              Container(color: Colors.green),
              Container(color: Colors.blue),
            ],
          ),
          SliverGrid.extent(
            maxCrossAxisExtent: 200,
            mainAxisSpacing: 10.0,
            crossAxisSpacing: 10.0,
            childAspectRatio: 4.0,
            children: <Widget>[
              Container(color: Colors.pink),
              Container(color: Colors.indigo),
              Container(color: Colors.orange),
              Container(color: Colors.pink),
              Container(color: Colors.indigo),
              Container(color: Colors.orange),
            ],
          ),
        ],
      ),
    );
  }
}

最後に

今回は SliverList & SliverGrid を紹介しました。
SliverList & SliverGrid といった Sliver は遅延読み込みができ、それぞれの要素が表示するときにビルドされるようになっているので、childrenの数が多い場合でもスムーズにスクロールできるようになります。リストやグリッドで表示する数が増えれば増えるほど、この Widgte の効果が分かると思います。
ぜひ試して効果を体験してみてください。
次は #13 FadeInImage です。またお会いしましょう。

参考記事

https://api.flutter.dev/flutter/widgets/SliverList-class.html
https://api.flutter.dev/flutter/widgets/SliverGrid-class.html
https://www.kindacode.com/article/flutter-sliverlist/
https://www.kindacode.com/article/flutter-slivergrid-example/

Discussion