Sliverで(動的な)画像のグリッドを実装する

# 概要

公式のSliverGridの動画に出てくる↓を、画像を展開するグリッドとして実装します。動的なコンテンツ表示でよく使うやつですね。
雑にググったら色付きのタイルを並べてるサンプルは出てくるけど画像を動的に並べるコードがあんまり出てこなかったので書きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201135/e044c31f-3ed9-02e1-9551-f55e1c97acde.png)

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

## 前提となるウィジェット

Scaffoldと、Sliverを使う時のお約束である`CustomScrolView`を置きます。

```dart
Scaffold(
  body: CustomScrolView()
)
```

画面はまだ真っ白です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201135/8cf2c647-a359-5bee-18f9-ae55d58adbd5.png)


## 公式の仰せのままにとりあえずSliverGridを置く

色々すっ飛ばしてる気もしますが、一旦公式のサンプルをそのままコピペします。

```dart
Scaffold(
  body: CustomScrollView(
    slivers: <Widget>[
      SliverGrid(
        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 200.0,
          mainAxisSpacing: 10.0,
          crossAxisSpacing: 10.0,
          childAspectRatio: 4.0,
        ),
        delegate: SliverChildBuilderDelegate(
          (BuildContext context, int index) {
            return Container(
              alignment: Alignment.center,
              color: Colors.teal[100 * (index % 9)],
              child: Text('grid item $index'),
            );
          },
          childCount: 20,
        ),
      ),
    ]
  )
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201135/19cd07d2-666e-ff37-fa9e-53cf1ac9685c.png)

画面ができました。

### 1行解説

このままでは解説になってないので使ってるプロパティを雑に訳してみます。

- `gridDelegate`
  - グリッドのサイズ自体をコントロールするウィジェット。縦横のサイズとか余白とかを管理する。
- `delegate`
  - 実際のグリッドの中に入るコンテンツを生成するウィジェットが入る。
 
- `SliverChildBuilderDelegate`
  - ビルダーのコールバックを使うsliverに子ウィジェットを提供することを責務にしたクラス。
  - コンストラクタにbuilderを取る。
  - sliverは全部lazy constructなので、画面に出てないwidgetは表示されるまでconstructが遅延される。この辺のややこしい処理を`SliverChildBuilderDelegate`から生成されたbuilderが行ってくれる。
  - 逆に言うと、画面から見えなくなったウィジェットはdestoryされる（はずな）ので、もし画面外のに出たウィジェットを生かしたままにしたいのであれば`addAutomaticKeepAlives: true`にする。


## 表示したい画像のリストを用意する

適当な画像のURLを拾ってきてリストにします。

```dart
_createSampleURLs() {
  final _urls = [];
  const _url =
    'https://kumamoto.photo/archives/_data/i/upload/2016/05/11/20160511090903-1c096f51-cu_e286.jpg';
  for (var i = 0; i < 20; i++) {
    _urls.add(_url);
  }
  return _urls;
}

final designURLs = _createSampleURLs();
```

## 出したいコンテンツの数を入れる

`SliverChildBuilderDelegate.childCount`に.lengthした数を入れます。ビルドされるタイルの数を制御します。


```dart
final designURLs = _createSampleURLs();

Scaffold(
  body: CustomScrollView(
    slivers: <Widget>[
      SliverGrid(
        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 200.0,
          mainAxisSpacing: 10.0,
          crossAxisSpacing: 10.0,
          childAspectRatio: 4.0,
        ),
        delegate: SliverChildBuilderDelegate(
          (BuildContext context, int index) {
            return // todo
              
          },
          childCount: designURLs.length, 
        ),
      ),
    ]
  )
```

## 出したいコンテンツのbuilderを定義する

最後に、出したいコンテンツのbuilderを定義します。childの数だけbuilderに指定した関数が実行されてタイルが1つ1つビルドされます。具体的には以下のような感じです。

```dart
final designURLs = _createSampleURLs();

Scaffold(
  body: CustomScrollView(
    slivers: <Widget>[
      SliverGrid(
        gridDelegate: 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(designURLs[index]),
                fit: BoxFit.fitWidth,
              ),
            );
          },
          childCount: designURLs.length, 
        ),
      ),
    ]
  )
```

画面は以下のようになるはず。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201135/6f519265-be1e-a9c4-7549-f969e5bc169a.png)


公式のコピペでそのまま来ましたが、`gridDelegate`はいじってればだいたい分かると思うので割愛します。

# まとめ

`SliverGrid`で動的にグリッドを実装しました。落ち着いて見ればそんなに難しくないはずなので頑張りましょう。