Roadmap.shで学ぶFlutter - Advanced Dart - [Lists]

# 導入

[roadmap.shのFlutter](https://roadmap.sh/flutter)のAdvanced Dartの章の2本目は`Lists`です。
Flutterでは、アプリケーション内でリストを表示するための様々な方法が提供されています。
今回は、Flutterでリストを表示する一般的な方法について、各ウィジェットのメリットやデメリット、具体的な実装例を交えながら解説していきます。

# Lists

Dart・Flutterでリストを表示する一般的な方法には、以下があります。

- ListViewウィジェット
- ListTileウィジェット
- SingleChildScrollViewとColumnの組み合わせ
- GridViewウィジェット
- CustomScrollViewとSliversの組み合わせ

これらのウィジェットを使うことで、スクロール可能なリストやグリッド、またはその両方を組み合わせた表示が可能です。
各アイテムの見た目は、ウィジェットやレイアウト、スタイリングを使用してカスタマイズできます。

## `ListView`と`ListTile`の利用
基本的にFlutterでListを表示するには、ListViewとListTileを利用します。
より詳細な利用方法としては、ListView.builderで動的なリストを生成し、各アイテムをListTileで表示するケースがほとんどです。
メニューリストや設定項目リストなどの一般的なリスト表示に利用されます。

### メリット
ListViewを利用するメリットは以下の通りです。

1. **簡単な実装**
ListViewは、簡単にリストを表示することができます。
2. **スクロール機能**
スクロール機能が標準で組み込まれており、縦方向のスクロールを利用することができます。
3. **パフォーマンスが高い**
ListView.builderを使用することで、大量のデータを効率的にリスト表示することができます。必要な部分だけをレンダリングするため、メモリ使用量を抑えることができます。
4. **柔軟性**
ListViewは、リストアイテムのカスタマイズが容易で、複雑なUIを実現可能です。また、ListTileは、複数のUI要素を1つのコンポーネントにまとめることで、リストアイテムのUIコードを簡素化します。
5. **インタラクション**
ListTileにはonTapやonLongPressなどのインタラクションイベントがデフォルトで備わっており、ユーザーインタラクションを簡単に処理することができます。

### デメリット
逆にデメリットも存在します。

1. **水平スクロールが制限される**
メリットにも記載した通り、ListViewはデフォルトで縦方向にスクロールになります。水平スクロールを実装するには、少し工夫が必要です。
2. **大規模なリストでのスクロールパフォーマンス**
単純なListViewでは、大量のアイテムを持つリストでスクロールのパフォーマンスが低下する可能性があります。
3. **複雑なレイアウトには向かない**
ListViewはリスト表示に特化しているため、複雑なレイアウトや階層構造には不向きです。

### 実装例

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('ListView Example'),
        ),
        body: ListView(
          children: <Widget>[
            ListTile(
              leading: Icon(Icons.map),
              title: Text('Map'),
            ),
            ListTile(
              leading: Icon(Icons.photo_album),
              title: Text('Album'),
            ),
            ListTile(
              leading: Icon(Icons.phone),
              title: Text('Phone'),
            ),
          ],
        ),
      ),
    );
  }
}
```

## `SingleChildScrollView`と`Column`
SingleChildScrollViewとColumnの組み合わせパターンのメリットとデメリットを説明していきます。

### メリット

1. **自由なレイアウトが可能**
`Column`を使うことで、縦方向の配置ができ、各要素の配置やスタイルを自由にカスタマイズできます。複雑なレイアウトや多様なUI要素を組み合わせる場合に適しています。

2. **簡単に使える**
小規模なリスト表示や、他のウィジェットと組み合わせて表示したい場合に非常にシンプルな実装ができます。

3. **スクロールしない部分との共存**
`SingleChildScrollView`の中にスクロールさせたい部分だけを配置し、その他の部分をスクロールさせないようにすることが簡単にできます。固定ヘッダーやフッターなどとの共存が簡単です。

### デメリット

1. **パフォーマンスの低下**
`SingleChildScrollView`は、リスト全体を一度にレンダリングするため、アイテム数が多い場合にはパフォーマンスが低下します。

2. **画面外のコンテンツのレンダリング**
すべての子ウィジェットが一度にレンダリングされるため、画面外の要素も描画され、メモリを無駄に消費します。

3. **リスト専用ではない**
`SingleChildScrollView`と`Column`はリスト表示専用ではないため、`ListTile`のようなリスト専用の機能を持ちません。そのため、リスト表示のためには手動でレイアウトや挙動をコントロールする必要があります。

4. **高さを固定する必要**
`Column`の子要素はすべての高さを明示的に設定する必要があるため、動的に生成されるリストに対しては適していない場合があります。`ListView`のように自動的に高さを調整する機能がないため、配置が難しい場合があります。

### 実装例

```dart
SingleChildScrollView(
  child: Column(
    children: [
      for (var item in items)
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              Icon(Icons.label),
              SizedBox(width: 10),
              Text(item.title),
            ],
          ),
        ),
      // ...
      ElevatedButton(
        onPressed: () {},
        child: Text('さらに読み込む'),
      ),
    ],
  ),
)
```

`SingleChildScrollView`と`Column`を使用して、リストアイテムを縦に並べ、`Row`ウィジェットを使ってカスタムレイアウトをしてみました。また、`Column`の最後にボタンなどの他のウィジェットを追加することも簡単です。

## `GridView`ウィジェット

次は、GridViewウィジェットのパターンを紹介していきます。

### メリット

1. **アイテムを整列させて表示できる**
   `GridView`は、アイテムをグリッド形式で表示することができます。写真ギャラリーや商品カタログなど、**同じサイズのアイテムを並べて表示**する場合に特に便利です。
2. **スクロール可能である**
   `GridView`はスクロール可能なグリッドを簡単に作成できます。大量のアイテムを縦だけでなく、横方向にスクロール表示できます。
3. **パフォーマンスに優れている**
   `ListView`の場合と同じく、`GridView.builder`を使用することで、表示領域のアイテムのみをレンダリングするため、メモリ使用量を抑え、パフォーマンスを維持できます。
4. **レイアウトの柔軟性**
   `GridView`は、アイテムの数やサイズに応じて、柔軟に列数やアイテムの幅、高さを設定できます。特に`SliverGridDelegate`を使用すると、カスタムレイアウトを簡単に実現できます。
5. **レスポンシブデザインに対応しやすい**
   画面のサイズに応じてグリッドの列数を動的に変更することできます。これにより、レスポンシブデザインに対応しやすくなります。特に横持ちのデバイスやタブレットなどに対応する際に便利です。

### デメリット

1. **横方向のレイアウトが難しい**
   `GridView`は主に縦スクロールに最適化されているため、横方向にスクロールさせる場合は設定が少し複雑になります。縦方向のレイアウトに比べて使い勝手が劣ります。
2. **カスタムレイアウトが難しい場合がある**
   異なるサイズのアイテムや特殊なレイアウトを組み合わせる場合には、設定が難しくなることがあります。`SliverGridDelegate`などを使いこなす必要があります。
3. **アイテム間のスペーシングが制限される**
   `GridView`は各アイテムのサイズや間隔を均一に設定することが前提となっています。そのため、**複雑な間隔設定を求めるデザインには不向き**です。特定のアイテムだけを異なる間隔にするのは難しいです。

### 実装例

```dart
GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2, // 列数
    crossAxisSpacing: 10.0, // 列間のスペース
    mainAxisSpacing: 10.0, // 行間のスペース
    childAspectRatio: 1.0, // アイテムの縦横比
  ),
  itemCount: items.length,
  itemBuilder: (context, index) {
    return GridTile(
      child: Image.network(items[index].imageUrl),
      footer: GridTileBar(
        backgroundColor: Colors.black54,
        title: Text(items[index].title),
      ),
    );
  },
)
```

この例では、`GridView.builder`を使用して2列のグリッドを作成し、各アイテムを`GridTile`でラップしています。

### `GridView`まとめ
`GridView`は、画像ギャラリーやカタログ表示のように、同じサイズのアイテムをグリッド形式で表示する場合に非常に便利です。
しかし、カスタムレイアウトや異なるサイズのアイテムを扱う場合には、設定が複雑になる可能性があります。
その場合は、他のパターンを検討する方が無難です。

## `CustomScrollView`と`Slivers`
CustomScrollViewとSliversの組み合わせのパターンを解説していきます。

### ## Sliverの基本概念

Sliverウィジェットは、主に`CustomScrollView`内で使用され、異なるタイプのスクロール可能なコンテンツを組み合わせることができます。
代表的なSliverウィジェットを3つ紹介します。

- **SliverList**: 複数の子要素をリスト形式で配置し、スムーズなスクロールを実現します。`CustomScrollView`の`slivers`プロパティに組み込んで使用します。
- **SliverGrid**: 子要素をグリッド形式で配置し、二次元的なレイアウトを提供します。これも`CustomScrollView`と組み合わせて使用します。
- **SliverAppBar**: スクロールに応じて動的に変化するアプリバーで、UXを向上させます。

### メリット

1. **高度なレイアウト制御が可能**
   `CustomScrollView`と`Slivers`を組み合わせることで、リストやグリッド、ヘッダー、フッターなどの異なるレイアウトを一つのスクロールビュー内で自由に組み合わせることができます。特に複雑なUIや異なるセクションを持つ画面で非常に役に立ちます。
2. **パフォーマンス**
   `Slivers`は、必要な部分だけをレンダリングするため、スクロールがスムーズでパフォーマンスが良好です。大量のアイテムがある場合にも、効率的に処理されます。
3. **異なるスクロール挙動を設定できる**
   `Slivers`を使用することで、特定のセクションやアイテムに対して異なるスクロール挙動や動きを設定できます。例えば、ヘッダーがスクロールと共に縮小したり、特定のアイテムが固定されたりするなど、リッチなUIを実現できます。
4. **セクションごとのレイアウトが可能**
   `SliverList`や`SliverGrid`を使って、リスト表示やグリッド表示をセクションごとに分けて表示できます。これにより、同じ画面上で異なる種類のコンテンツを視覚的に分離することが可能です。
5. **完全なカスタマイズが可能**
   `Slivers`は非常に柔軟で、デフォルトのリストやグリッドウィジェットでは実現できない複雑なカスタムUIが作成可能です。自由に自分の望むレイアウトを構築できます。

### デメリット

1. **学習コストが高い**
   `Slivers`を使いこなすには、Flutterの基本的なウィジェットよりも多くの知識と経験が必要です。慣れていないと、実装が複雑で理解が難しい場合があります。私もまだきちんとした理解はできていません。。
2. **コードが複雑になりがち**
   コードが複雑化しやすく、メンテナンスが難しくなる可能性があります。特に大規模なプロジェクトや多くのセクションを持つ画面では、コードの整理が重要になります。
3. **過剰な使用によるパフォーマンス低下**
   `Slivers`を過剰に使用すると、逆にパフォーマンスに悪影響を与える可能性があります。特に、多くのカスタム要素や複雑なアニメーションを導入する際には、慎重に最適化を行う必要があります。
4. **デバッグが難しい**
   複雑なUIを構築する際、バグの発見やデバッグが難しくなることがあります。特に、異なる`Sliver`間の相互作用やスクロールの挙動に問題が発生した場合、解決が困難になることがあります。
5. **再利用性が低い**
   `Slivers`を使用すると、非常に特化したレイアウトが作成されるため、再利用性が低下することがあります。他のプロジェクトや画面で使い回すのが難しくなることがあります。

### 実装例

```dart
CustomScrollView(
  slivers: <Widget>[
    SliverAppBar(
      expandedHeight: 200.0,
      floating: false,
      pinned: true,
      flexibleSpace: FlexibleSpaceBar(
        title: Text('Sliver Example'),
        background: Image.network(
          'https://example.com/image.jpg',
          fit: BoxFit.cover,
        ),
      ),
    ),
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) {
          return ListTile(
            title: Text('Item #$index'),
          );
        },
        childCount: 20,
      ),
    ),
    SliverGrid(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        crossAxisSpacing: 10.0,
        mainAxisSpacing: 10.0,
      ),
      delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) {
          return Container(
            color: Colors.blue[100 * (index % 9)],
            child: Center(
              child: Text('Grid Item #$index'),
            ),
          );
        },
        childCount: 10,
      ),
    ),
  ],
)
```

この例では、`CustomScrollView`に`SliverAppBar`、`SliverList`、および`SliverGrid`を組み合わせています。これにより、スクロール可能なアプリバー、リストアイテム、グリッドアイテムを持つレイアウトを作成できます。`SliverAppBar`はスクロール時に縮小し、`SliverList`と`SliverGrid`はそれぞれ異なるコンテンツをスクロール可能なセクションとして表示します。

### `CustomScrollViewとSliversの組み合わせ`まとめ
CustomScrollViewとSliversの組み合わせのケースは、複雑で高度なレイアウトを必要とするアプリケーションに最適です。パフォーマンスと柔軟性に優れており、独自のデザインや動きを取り入れたい場合に有効です。しかし、学習コストや実装の複雑さが高いため、簡単なリストやグリッド表示には他のパターンを検討する方が良さそうです。

# 終わりに
Flutterでリスト表示するためのウィジェットは非常に多彩です。シンプルなリストから複雑なカスタムレイアウトまで、幅広いニーズに対応することができます。今回紹介した`ListView`や`GridView`、`CustomScrollView`といったウィジェットを活用することで、効率的かつパフォーマンスの高いリスト表示を実現することができます。それぞれのウィジェットの特性を理解し、アプリケーションの要件に合わせた選択を行うことで、ユーザーにとって魅力的なUIを提供していきましょう！！