CustomScrollViewを利用して一画面に複数のスクロール要素を構築する

画面にリストやグリッド、画像など、様々な要素を挿入したい、という状況は結構あると思う。

しかし、リスト自体がスクロール要素を複数合わせると、思うように動いてくれない状況が発生した。

例えば下記のような要件を満たす画面を作成するとする。
 
 - 画像や文言など、規則性の無い要素を縦に並べる
 - 画面全体はスクロールできるようにする

イメージ
<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81348/fcd8927d-7a69-d7b6-458b-2ba4901a578a.png">

コードは下記のようになる

```dart:main.dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue,),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    String img_src1 = 'https://example.com/img1.jpg';
    String img_src2 = 'https://example.com/img2.jpg';
    String img_src3 = 'https://example.com/img3.jpg';
    String img_src4 = 'https://example.com/img4.jpg';
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: <Widget>[
            Text('example', style: TextStyle(fontSize: 30.0, color: Colors.amber),),
            Image.network(img_src1, height: 200,),
            Text('example', style: TextStyle(fontSize: 30.0, color: Colors.black45),),
            Image.network(img_src2, height: 200,),
            Text('example.example.example.example.example.example.', style: TextStyle(fontSize: 30.0, color: Colors.deepOrangeAccent),),
            Text('example.example.example.example.example.example.', style: TextStyle(fontSize: 30.0, color: Colors.teal),),
            Image.network(img_src3, width: 400,),
            Text('example', style: TextStyle(fontSize: 30.0, color: Colors.indigo),),
            Image.network(img_src4, width: 400,),
          ],
        ),
      ),
    );
  }
}
```

注意点は以下の通り

 - 規則性の無い要素を並べるので、`ListView`等ではなく、`Column`を使用
    - 文言だけ or 写真だけ、ということであれば、`ListView`の方が使い勝手はいいと思うが、今回は１内容に複数の要素が入っている
 - 画面からはみ出した分をスクロールできるように、`SingleChildScrollView`で全体を囲む
<br><br>



# 他のスクロール要素を追加する

この画面に新しく文言をタイル状に表示するエリアを作ってみるために、下記のような`GridView`要素を入れてビルドすると、例外が発生する。

```dart:main.dart
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    String img_src1 = 'https://example.com/img1.jpg';
    // 〜省略部分〜
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: <Widget>[
            Text('example', style: TextStyle(fontSize: 30.0, color: Colors.amber),),
            // 〜省略部分〜
            Image.network(img_src4, width: 400,),
            // 新しく追加した部分↓
            GridView.count(
              crossAxisCount: 5,
              children: List.generate(100, (index) {
                return Center(
                  child: Text(
                    'Item $index',
                    style: Theme.of(context).textTheme.headline,
                  ),
                );
              }),
            )
          ],
        ),
      ),
    );
  }
}
```
これは、スクロール要素を入れ子にすることによって、縦の長さが無限になってしまうのを防ぐために、発生する例外とのこと。


# CustomScrollViewでリストやグリッドを混在させる
CustomScrollViewとSliverを利用することで、リストやグリッドを好きに配置できるようになる。

```dart:main.dart
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    String img_src1 = 'https://example.com/img1.jpg';
    // 〜省略部分〜
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: CustomScrollView(
        slivers: <Widget>[
          SliverList(
            delegate: SliverChildListDelegate([
              Column(
                children: <Widget>[
                  Text('example', style: TextStyle(fontSize: 30.0, color: Colors.amber),),
                  // 〜省略部分〜
                  Image.network(img_src4, width: 400,)
                ]
              )
            ]),
          ),
          SliverGrid(
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 5,
            ),
            delegate: SliverChildBuilderDelegate(
                  (BuildContext context, int index) {
                return Center(
                  child: Text(
                    'Item $index',
                    style: Theme.of(context).textTheme.headline,
                  ),
                );
              },
              childCount: 100,
            )
          ),
        ]
      ),
    );
  }
}
```

変更点と留意点は以下の通り

    - `SingleChildScrollView`から`CustomScrollView`に変更する
    - イメージや文言を格納していた`Column`要素を`SliverList`の中に入れる
    - `GridView`部分は`SliverGrid`に変更

<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81348/8ebf9c1a-0c3d-0e0a-f214-479a24b5e210.png">

無事表示されるようになった。