【Flutter】GridView入門!一覧表示を簡単に実現

対象者

  • Flutterを使用した開発経験はあるが、GridViewの具体的な使い方に不安を感じている方
  • クライアントや上司からの要求に迅速に対応したいフロントエンド開発者の方
  • ユーザー体験を向上させるための効果的なレイアウトを学びたい方

はじめに

Flutterを使ってアプリを開発しているあなた。GridViewの実装に頭を悩ませたことはありませんか?ここでは、GridViewの基本から高度な設定、さらには他のFlutterウィジェットとの連携方法までを網羅的に解説します。

GridViewというのは、「二次元の格子状に配置された項目や情報を表示するビュー」を示します。
Flutterにおいては、「多数のアイテムや情報を格子状に整然と表示する」ウィジェットです。そのため、写真アルバムや商品一覧など、多くの情報を一度に見やすく表示することができます。
実際のアプリとしては、写真共有アプリやショッピングアプリなどのケースに「ユーザーが一覧から商品や写真を選びやすくする」というような機能を実現することができます。

この記事を読めば「GridView、思ったよりも簡単に使えるんだ!」と実感することでしょう!

GridViewの基本

FlutterのUI開発において、リストやグリッドの表示は非常に一般的な要件です。特に、アイテムを2次元のレイアウトで整然と表示する場合、GridViewはその要求に応えるフレキシブルなウィジェットとして知られています。

GridViewとは何か?

GridViewはFlutterで2次元のスクロール可能なリストを作成するためのウィジェットです。一般的に、写真やアイコンなどのビジュアル要素を整然としたグリッド形式で表示する際に使用されます。
縦方向、横方向の両方にスクロールすることが可能で、多様なカスタマイズが許容されるため、アプリケーションの多くのシーンで役立ちます。

GridViewの主な利用シーン

GridViewは多岐にわたるシーンで利用されますが、以下はその主な利用シーンのいくつかです。

  1. 写真ギャラリーアプリ: スマートフォンのギャラリーアプリでは、ユーザーが撮影した写真をグリッド形式で表示するのが一般的です。

  2. 商品一覧ページ: オンラインショッピングアプリにおいて、商品をグリッド形式で表示することで、一度に多くの商品をユーザーに提示することができます。

  3. アイコンベースのメニュー: いくつかのアプリでは、ホーム画面にアイコンをグリッド形式で配置し、各機能へのアクセスを提供します。

これらのシーンはGridViewの使い道を示す一部に過ぎませんが、機能と柔軟性の両方を持つこのウィジェットが、多くのアプリ開発者にとって魅力的な選択肢であることがわかります。

総じて、GridViewはFlutterでの2次元リスト表示の要件に応えるための強力なツールとして、幅広い利用シーンで活躍しています。その使いやすさとカスタマイズの自由度から、多くのFlutter開発者が頼りにしているウィジェットの一つと言えるでしょう。

GridViewの構成方法

Flutterアプリケーションでのグリッド表示は多種多様なレイアウトを必要とするため、GridViewウィジェットはいくつかの構築方法を提供しています。以下、その中でも主な3つの方法を紹介します。

GridView.countの利用方法

GridView.countは、指定した数の列数を持つグリッドを容易に作成するためのメソッドです。一般的なグリッドレイアウトを手軽に実現します。
主要なパラメータとしてcrossAxisCountがあり、これによりグリッドの列数を指定することができます。

GridView.count(
  crossAxisCount: 2,
  children: List.generate(100, (index) {
    return Center(
      child: Text('Item $index'),
    );
  }),
)

この例では、2列のグリッドが作成され、100のアイテムが表示されます。

GridView.builderの基本的な使い方

大量のアイテムを持つグリッドや動的にアイテムが変わる場合には、GridView.builderを使用するのが最適です。
GridView.builderは遅延読み込みをサポートしているため、表示されるアイテムのみをレンダリングします。これにより、パフォーマンスの最適化が図られます。

GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
  ),
  itemBuilder: (BuildContext context, int index) {
    return Image.network('https://example.com/image_$index.jpg');
  },
)

この例では、3列のグリッドが作成され、指定されたURLから画像を動的に取得して表示します。

GridView.extentとその特徴

GridView.extentは、アイテムの最大横幅を指定してグリッドを作成するメソッドです。
画面のサイズやデバイスの解像度に関係なく、一定のアイテムの横幅を持つグリッドを作成したい場合に有用です。maxCrossAxisExtentプロパティを使用して、アイテムの最大横幅を指定します。

GridView.extent(
  maxCrossAxisExtent: 200,
  children: List.generate(100, (index) {
    return Center(
      child: Text('Item $index'),
    );
  }),
)

この例では、アイテムの最大横幅を200ピクセルに設定して、グリッドが表示されます。

総じて、FlutterのGridViewは、さまざまなニーズに対応するための複数の構築方法を提供しています。それぞれの方法がどのようなシーンで適しているかを理解することで、効率的なアプリケーションの構築が可能になります。

実用的なGridViewの例

FlutterのGridViewは、その汎用性とカスタマイズ性により、多くの実用的なシーンで利用されています。以下では、その具体的な使用例をいくつか取り上げて紹介します。

写真レイアウトでのGridViewの使用

GridViewは、写真や画像を一覧表示する際のレイアウトとして非常に効果的です。
多くのアプリケーションやウェブサイトでは、写真をグリッド形式で表示することが一般的です。GridViewはこのようなレイアウトを簡単に実現できるため、多くの開発者に選ばれています。

GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
  ),
  itemBuilder: (BuildContext context, int index) {
    return Image.network('https://example.com/photos/photo_$index.jpg');
  },
)

この例では、3列のグリッドで写真を表示しています。

ダイナミックなリストからのグリッドの作成

データの量や内容が動的に変わるリストからも、GridViewを使用して効果的にレイアウトを作成できます。

ユーザーのインタラクションや外部データの変更に応じて内容が変わるリストでは、GridView.builderを使用することで、動的にアイテムを生成し表示することができます。

List<String> dynamicList = fetchData(); // 外部データを取得する関数

GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemCount: dynamicList.length,
  itemBuilder: (BuildContext context, int index) {
    return Text(dynamicList[index]);
  },
)

GridViewのスクロール: 横と縦

GridViewはデフォルトで縦スクロールが有効になっていますが、横スクロールにも対応しています。
UIデザインのニーズに応じて、グリッドのスクロール方向を変更したい場面があります。Flutterはこのような要求にも柔軟に応えられる設計になっています。

GridView(
  scrollDirection: Axis.horizontal, // 横スクロールを指定
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  children: List.generate(50, (index) {
    return Center(child: Text('Item $index'));
  }),
)

この例では、2列のグリッドを横スクロールで表示しています。

GridViewの実用的な使用例はこれらだけではありませんが、上記の例を参考に、アプリケーションの要件に合わせた最適なレイアウトを作成することができます。

GridViewの高度な設定とカスタマイズ

Flutterでのレイアウト作成において、GridViewはその柔軟性とカスタマイズ性から頼りにされるウィジェットの一つです。以下では、そのカスタマイズの詳細について詳しく説明します。

スペースとアスペクト比の調整方法

GridViewでのアイテム間のスペースやアスペクト比は、gridDelegateプロパティを通じて簡単に調整できます。
デザインの要件に応じて、アイテムのサイズや間隔を調整することが求められる場面があります。Flutterはこれを容易に実現できるツールを提供しています。

GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
    mainAxisSpacing: 10.0,  // 主要軸(縦)の間隔
    crossAxisSpacing: 10.0, // 横軸の間隔
    childAspectRatio: 3/4,  // アスペクト比
  ),
  itemBuilder: (context, index) => Image.asset('path/to/image.jpg'),
)

SliverGridDelegateWithFixedCrossAxisCountSliverGridDelegateWithMaxCrossAxisExtent の使用

これらのクラスは、GridViewのアイテムの配置やサイズのカスタマイズを支援するためのものです。
適切なデザインやレイアウトを実現するために、異なるケースでのアイテムの配置方法やサイズを変更する必要があります。

// 固定数の列を持つGridView
GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 4,
  ),
  itemBuilder: (context, index) => Image.asset('path/to/image.jpg'),
)

// 最大幅を設定して、その範囲で動的にアイテム数を調整するGridView
GridView.builder(
  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 200.0,
  ),
  itemBuilder: (context, index) => Image.asset('path/to/image.jpg'),
)

カスタムScrollViewとの組み合わせ

CustomScrollViewGridView を組み合わせることで、より高度なスクロール動作やレイアウトを実現できます。
特定のレイアウト要件や複雑なスクロール動作が求められる場合、FlutterはCustomScrollViewを提供しており、これとGridViewを組み合わせることで、高度なカスタマイズが可能になります。

CustomScrollView(
  slivers: <Widget>[
    SliverGrid(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
      ),
      delegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) {
          return Image.asset('path/to/image.jpg');
        },
        childCount: 20,
      ),
    ),
  ],
)

上記の方法を取り入れることで、FlutterのGridViewを活用し、ユーザーにとって魅力的なUIを提供することができます。

Flutterのウィジェットとの連携

Flutterの強力な点の一つは、異なるウィジェット同士のシームレスな連携です。ここでは、GridViewをさらに強化するために、いくつかの主要なウィジェットとの連携方法を紹介します。

ListViewとの連携

ListViewGridViewは、同時に使用することで、さまざまなアイテム表示スタイルを組み合わせることができます。
リストとグリッドの組み合わせは、多くのアプリケーションで見られる一般的なデザインパターンです。これにより、ユーザーは情報を効果的に認識・消化することができます。

ListView(
  children: <Widget>[
    ListTile(title: Text('セクションタイトル')),
    Container(
      height: 200,
      child: GridView.builder(
        scrollDirection: Axis.horizontal,  // 横スクロール
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
        ),
        itemBuilder: (context, index) => Image.asset('path/to/image.jpg'),
      ),
    ),
    ListTile(title: Text('別のセクション')),
  ],
)

ListTileの使用例

ListTileは、一貫性のあるリストアイテムのデザインを簡単に実現するためのウィジェットです。
ListTileは、主要なタイトルやサブタイトル、先行や後続のアイコンなど、リストアイテムに一般的に必要な要素を効果的に配置します。

 GridView.builder(
          scrollDirection: Axis.horizontal,
          itemCount: items.length,
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                childAspectRatio: 0.5,
          ),
          itemBuilder: (context, index) {
                return ListTile(
                  leading: Icon(Icons.photo),
                  title: Text(items[index]),
                  subtitle: Text(items[index]),
                  trailing: Icon(Icons.more_vert),
                );
          }

Q&A

以下は、マークダウン形式でのQ&Aリストです:

Q: GridViewウィジェットとは何ですか?

A: GridViewは、Flutterで2次元のスクロール可能なリストを作成するためのウィジェットです。アイテムは格子状に配置され、スクロールの方向は通常縦方向ですが、必要に応じて横方向にも変更することができます。特に、多数のアイテムやアイコンを格子状に整理して表示する際に使用されます。

Q: GridViewのコンストラクタはどのように使い分けますか?

A: GridViewには複数のコンストラクタがあります。主に次の3つがあります:

  • GridView.builder:アイテムの数が動的である場合や、大量のアイテムを持つ場合に役立ちます。アイテムが表示されるときのみ、アイテムをビルドします。
  • GridView.count:グリッドの交差軸に何個の子があるかを指定して、グリッドを作成します。
  • GridView.extent:交差軸の子の「最大ピクセル幅」を指定して、グリッドを作成します。

使い道と必要に応じて、適切なコンストラクタを選択することができます。

Q: GridView内で各要素のPaddingをどのように調整しますか?

A: GridViewの各要素のPaddingを調整するには、GridTile やカスタムのウィジェット内で Padding ウィジェットを使用します。例えば、GridView.builderを使用する場合、 itemBuilderの中で、各アイテムをビルドする際にPadding ウィジェットを用いてPaddingを調整できます。

Q: GridViewを複数表示するため、GridViewをListViewに入れようとすると、エラーが発生する

A: ListView内にGridViewを入れると、スクロールの制約やレイアウトの問題が発生することがあります。特に「Vertical viewport was given unbounded height」というエラーは、GridViewが無限の高さを持つために起こります。このエラーは、スクロール可能なWidget(ListView)の中に、もう一つのスクロール可能なWidget(GridView)をネストすると発生しやすいです。

このエラーを解決するには、GridViewphysicsプロパティにNeverScrollableScrollPhysics()を設定して、GridViewのスクロールを無効化します。また、shrinkWrapプロパティをtrueに設定することで、GridViewが自身の内容の高さに合わせてサイズを調整するようにします。

GridView.builder(
  physics: NeverScrollableScrollPhysics(), // GridViewのスクロールを無効化
  shrinkWrap: true, // GridViewの高さをコンテンツに合わせる
);

上記のコードを用いることで、ListView内でGridViewが適切に動作し、エラーを回避できます。

まとめ

Flutterでのグリッドレイアウトの実装は驚くほど柔軟です。特にGridViewウィジェットは、動的なアイテム配置や可変の高さを持つアイテムのレイアウトなど、多彩なレイアウトを作成するのに最適です。

Flutterのこのような強力な特性を学ぶことで、アプリのUI/UXデザインの可能性が飛躍的に広がります。これらの情報とテクニックを駆使して、より魅力的なアプリケーションを作成しましょう。

参考

ソース(main.dartにコピペして動作確認用)

基本

import 'package:flutter/material.dart';

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

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

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<String> items = List.generate(100, (index) => 'Item $index');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GridView Sample'),
      ),
      body: GridView.builder(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3, // 3列のGridView
          childAspectRatio: 1.0, // アイテムのアスペクト比
          mainAxisSpacing: 4.0, // 縦の間隔
          crossAxisSpacing: 4.0, // 横の間隔
        ),
        itemBuilder: (context, index) {
          return GestureDetector(
            onTap: () {
              print('${items[index]} was tapped!');
            },
            child: Card(
              child: Center(
                child: Text(items[index]),
              ),
            ),
          );
        },
        itemCount: items.length,
      ),
    );
  }
}

このサンプルコードでは以下の特性を組み込んでいます:

  1. GridView.builderを使用して動的にアイテムを生成
  2. SliverGridDelegateWithFixedCrossAxisCountを使って3列のグリッドを作成
  3. 各アイテムはカード形式で表示
  4. アイテムをタップするとそのアイテム名がコンソールに表示される

応用例

import 'package:flutter/material.dart';

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

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

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<String> items = List.generate(100, (index) => 'Item $index');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GridView Sample'),
      ),
      body: ListView(
        children: [
          ListTile(title: Text('縦タイル')),
          SizedBox(
            height: 200,
            child: GridView.builder(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 8,
              ),
              itemBuilder: (context, index) {
                return Card(
                  child: Center(child: Text(items[index])),
                );
              },
              itemCount: items.length,
            ),
          ),
          ListTile(title: Text('横タイル')),
          SizedBox(
            height: 300,
            child: GridView.builder(
              scrollDirection: Axis.horizontal,
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                childAspectRatio: 0.5,
              ),
              itemBuilder: (context, index) {
                return ListTile(
                  leading: Icon(Icons.photo),
                  title: Text(items[index]),
                  subtitle: Text(items[index]),
                  trailing: Icon(Icons.more_vert),
                );
              },
              itemCount: items.length,
            ),
          ),
          ListTile(title: Text('横タイル')),
          SizedBox(
            height: 300,
            child: GridView.builder(
              scrollDirection: Axis.horizontal,
              itemCount: items.length,
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                childAspectRatio: 0.5,
              ),
              itemBuilder: (context, index) {
                return ListTile(
                  leading: Icon(Icons.photo),
                  title: Text(items[index]),
                  subtitle: Text(items[index]),
                  trailing: Icon(Icons.more_vert),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

FlutterのUI実装でよく使われるグリッド表示について、今回のサンプルコードを通して細かく解説します。特に、このサンプルでは、縦横のスクロールに対応したGridViewの実装方法や、ListTileを用いた豊富な表現方法に注目してみてください。

スクロール可能なリストビュー

まず、全体の構造としては、Scaffoldbody 内に配置された ListView で包まれています。これにより、アプリ全体がスクロール可能となっており、中に含まれる複数のGridViewもスムーズにスクロールしながら閲覧できます。

縦スクロールのGridView

コードの中で最初に登場するのは、縦にスクロール可能なGridViewです。このGridViewは、GridView.builder を使用して実装されており、その高さは SizedBox を使用して200と明示的に指定されています。8列のグリッドレイアウトが採用されており、各タイルにはアイテムの番号が表示されるシンプルなデザインとなっています。

横スクロールのGridView

続いて、サンプルコードには2つの横にスクロール可能なGridViewが用意されています。これらのGridViewでは、アイテム表示の方法として、ListTile ウィジェットが用いられています。ListTile は、leadingtitlesubtitletrailing など、様々な部分にコンテンツを配置することができるため、リッチな表現が可能です。今回のサンプルでは、アイコンやテキスト、さらにはサブテキストやアクションアイコンなど、その多様性をフルに活用した表現を見ることができます。