MDC-102 Flutter: マテリアルの構造とレイアウト(Flutter)

logo_components_color_2x_web_96dp.png

マテリアル コンポーネント(MDC)は、デベロッパーがマテリアル デザインを実装する際に役立ちます。Google のエンジニアと UX デザイナーのチームが作成した MDC には、美しく機能的な UI コンポーネントが多数含まれており、Android、iOS、ウェブ、Flutter.material.io/develop で利用できます。

Codelab MDC-101 では、2 つのマテリアル コンポーネント(テキスト フィールドと、インクの波紋付きのボタン)を使用してログインページを作成しました。今度は、この基礎にナビゲーション、構造、データを追加していきましょう。

作成するアプリの概要

この Codelab では、衣類と生活雑貨を販売する e コマースアプリ Shrine のホーム画面を作成します。これには次のものが含まれます。

  • トップ アプリバー
  • 商品が並んだグリッドリスト

Android

iOS

この Codelab の MDC コンポーネント

  • トップ アプリバー
  • グリッド
  • カード

Flutter 開発のご経験についてお答えください。

初心者 中級者 上級者

準備

Flutter でモバイルアプリを開発するには、以下の作業が必要です。

  1. Flutter SDK をダウンロードしてインストールします。
  2. Flutter SDK で PATH を更新します。
  3. Flutter プラグインと Dart プラグインとともに Android Studio をインストールするか、お好みのエディタをインストールします。
  4. Android Emulator または iOS シミュレータ(Xcode をインストールした Mac が必要)をインストールするか、物理デバイスを使用します。

Flutter のインストールについて詳しくは、スタートガイド: インストールをご覧ください。エディタの設定方法については、スタートガイド: エディタの設定をご覧ください。Android Emulator をインストールする場合は、最新のシステム イメージを備えた Pixel 3 スマートフォンなどのデフォルトのオプションを利用してください。VM アクセラレーションの有効化は、推奨されますが必須ではありません。上に述べた 4 つの手順を完了したら、Codelab を続行できます。この Codelab を完了するために必要なのは、1 つのプラットフォーム(Android または iOS)に Flutter をインストールすることだけです。

Flutter SDK が正しい状態にあることを確認する

この Codelab を先に進める前に、SDK が正しい状態にあることを確認します。Flutter SDK を以前にインストールしている場合は、flutter upgrade を使用して SDK が最新の状態であることを確認します。

 flutter upgrade

flutter upgrade を実行すると flutter doctor. が自動的に実行されます。これが最新の Flutter インストールでありアップグレードが不要だった場合は、手動で flutter doctor を実行します。設定を完了するためになんらかの依存関係をインストールする必要があるかどうかが通知されます。自分に関係がないチェックマークは無視してかまいません(たとえば、iOS 向けの開発を行う予定がない場合は Xcode を無視できます)。

 flutter doctor

よくある質問

MDC-101 から続行する場合

MDC-101 を完了していれば、コードはこの Codelab 用に準備されています。「トップ アプリバーを追加する」のステップに進んでください。

ゼロから始める場合

Codelab のスターター アプリをダウンロードする

スターター アプリをダウンロード

スターター アプリは material-components-flutter-codelabs-102-starter_and_101-complete/mdc_100_series ディレクトリにあります。

GitHub からクローンを作成する

GitHub からこの Codelab のクローンを作成するには、次のコマンドを実行します。

git clone https://github.com/material-components/material-components-flutter-codelabs.git
cd material-components-flutter-codelabs/mdc_100_series
git checkout 102-starter_and_101-complete

プロジェクトをセットアップする

以下の手順は、Android Studio(IntelliJ)を使用することを前提としています。

プロジェクトを開く

1. Android Studio を開きます。

2. ウェルカム画面が表示されたら、[Open an existing Android Studio project] をクリックします。

3. material-components-flutter-codelabs/mdc_100_series ディレクトリに移動し、[Open] をクリックします。プロジェクトが開きます。プロジェクトを一旦作成し終えるまで、Dart Analysis に表示されるエラーは無視してかまいません。

4. プロンプトが表示されたら、次の操作を行います。

  • 任意のプラットフォームやプラグインのアップデートまたは FlutterRunConfigurationType をインストールします。
  • Dart または Flutter SDK が設定されていない場合は、Flutter プラグインの Flutter SDK パスを設定します。
  • Android フレームワークを設定します。
  • [Get dependencies] または [Run ‘flutter packages get'] をクリックします。

Android Studio を再起動します。

スターター アプリを実行する

以下の手順は、Android エミュレータまたはデバイスでテストすることを前提としていますが、Xcode がインストールされている場合は、iOS シミュレータまたはデバイスでテストすることもできます。

1. デバイスまたはエミュレータを選択します。Android Emulator がまだ起動していない場合は、[Tools] -> [Android] -> [AVD Manager] を選択して仮想デバイスを作成し、エミュレータを起動します。AVD がすでに存在する場合は、次のステップに示すように、Android Studio のデバイス セレクタから直接エミュレータを起動できます(iOS シミュレータの場合、まだ起動していなければ、開発マシンで [Flutter Device Selection] -> [Open iOS Simulator] を選択してシミュレータを起動します)。

2. Flutter アプリを起動します。

  • エディタ画面上部にある [Flutter Device Selection] プルダウン メニューでデバイスを選択します(たとえば、<version> 向けにビルドされた Android SDK または iPhone SE など)。
  • Play アイコン()を押します。

これで、MDC-101 Codelab で作成した Shrine のログインページがシミュレータまたはエミュレータに表示されるはずです。

Android

iOS

ログイン画面が正常に表示されたので、アプリに商品を追加してみましょう。

現時点で、[Next] ボタンをクリックすると「You did it!」というホーム画面が表示されます。ここまでは問題ないのですが、今のところユーザーは何もできず、アプリ内のどこにいるのかわからない状態です。そこで、ナビゲーションを追加します。

マテリアル デザインには、高度なユーザビリティを実現するナビゲーション パターンがあります。特に目立つコンポーネントの 1 つが、トップ アプリバーです。

ナビゲーションを提供し、ユーザーが他のアクションにすばやくアクセスできるように、トップ アプリバーを追加しましょう。

AppBar ウィジェットを追加する

home.dart で、AppBar を Scaffold に追加します。

  // TODO: Add app bar (102)
  appBar: AppBar(
    // TODO: Add buttons and title (102)
  ),

AppBar を Scaffold の appBar: フィールドに追加すると、AppBar をページ上部に、本文を下部に配置したまま、完璧なレイアウトを無料で実現できます。

プロジェクトを保存します。Shrine アプリが更新されたら、[Next] をクリックしてホーム画面を表示します。

Android

iOS

AppBar は良さそうですが、タイトルが必要です。

テキスト ウィジェットを追加する

home.dart で、AppBar にタイトルを追加します。

// TODO: Add app bar (102)
  appBar: AppBar(
    // TODO: Add buttons and title (102)
    title: Text('SHRINE'),
    // TODO: Add trailing buttons (102)

プロジェクトを保存します。

Android

iOS

アプリバーは多くの場合、タイトルの横にボタンがあります。アプリにメニュー アイコンを追加しましょう。

先頭の IconButton を追加する

引き続き home.dart で、AppBar の leading: フィールドに IconButton を設定します(title: フィールドの前に挿入して、先頭から末尾への順番を模倣します)。

    // TODO: Add buttons and title (102)
    leading: IconButton(
      icon: Icon(
        Icons.menu,
        semanticLabel: 'menu',
      ),
      onPressed: () {
        print('Menu button');
      },
    ),

プロジェクトを保存します。

Android

iOS

メニュー アイコン(別名「ハンバーガー」)が、想定したとおりの場所に表示されます。

ボタンをタイトルの末尾に追加することもできます。Flutter では、これを「アクション」といいます。

アクションを追加する

IconButtons をさらに 2 つ追加するスペースがあります。

タイトルの後で、AppBar インスタンスに追加します。

// TODO: Add trailing buttons (102)
actions: <Widget>[
  IconButton(
    icon: Icon(
      Icons.search,
      semanticLabel: 'search',
    ),
    onPressed: () {
      print('Search button');
    },
  ),
  IconButton(
    icon: Icon(
      Icons.tune,
      semanticLabel: 'filter',
    ),
    onPressed: () {
      print('Filter button');
    },
  ),
],

プロジェクトを保存します。ホーム画面は次のようになります。

Android

iOS

アプリに、先頭のボタン、タイトル、右側の 2 つのアクションが表示されます。アプリバーには、コンテンツとは別のレイヤにあることを示す微妙な影を使用して高度も表示されます。

アプリの構造がある程度できたので、コンテンツをカードに入れて整理しましょう。

GridView を追加する

まずは、トップ アプリバーの下にカードを 1 つ追加しましょう。Card ウィジェットだけでは情報が足りず、表示する場所をレイアウトできません。そのため、GridView ウィジェットにカプセル化します。

Scaffold の body の Center を GridView に置き換えます。

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  // TODO: Build a grid of cards (102)
  children: <Widget>[Card()],
),

コードについて解説します。GridView は、表示するアイテムの数が無限ではなくカウント可能であるため、count() コンストラクタを呼び出します。しかし、レイアウトを定義するための情報が必要です。

crossAxisCount: には横方向のアイテム数を指定します。2 列にします。

padding: フィールドは、GridView の 4 辺すべてにスペースを持たせます。もちろん、隣に GridView の子はまだないため、後ろ側または下側にパディングは表示されません。

childAspectRatio: フィールドは、アスペクト比(高さに対する幅)に基づいてアイテムのサイズを指定します。

デフォルトでは、GridView によってタイルがすべて同じサイズで作成されます。

まとめて、GridView は各子の幅を ([width of the entire grid] - [left padding] - [right padding]) / number of columns と計算します。今回の値を使用すると ([width of the entire grid] - 16 - 16) / 2 となります。

高さは、アスペクト比を適用して幅から計算します(([width of the entire grid] - 16 - 16) / 2 * 9 / 8)。8 と 9 を反転させた理由は、幅から始めて高さを計算するからであって、その逆ではありません。

カードが 1 つありますが、空です。カードに子ウィジェットを追加しましょう。

コンテンツをレイアウトする

カードには、画像、タイトル、セカンダリ テキストの領域が必要です。

GridView の子を更新します。

// TODO: Build a grid of cards (102)
children: <Widget>[
  Card(
    clipBehavior: Clip.antiAlias,
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        AspectRatio(
          aspectRatio: 18.0 / 11.0,
          child: Image.asset('assets/diamond.png'),
        ),
        Padding(
          padding: EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text('Title'),
              SizedBox(height: 8.0),
              Text('Secondary Text'),
            ],
          ),
        ),
      ],
    ),
  )
],

このコードは、子ウィジェットを縦方向にレイアウトするために使用する Column ウィジェットを追加します。

crossAxisAlignment: fieldCrossAxisAlignment.start を指定します。これは「テキストを前縁に揃える」という意味です。

AspectRatio ウィジェットは、指定された画像の種類に関係なく、画像の形状を決定します。

Padding は、テキストを側面から少し中に入れます。

2 つの Text ウィジェットは、間に 8 ポイントの空白(SizedBox)を挟んで縦方向に積み重ねられます。Padding の中に収めるために、Column をもう 1 つ作成します。

プロジェクトを保存します。

Android

iOS

このプレビューでは、角の丸い、影付き(カードの高度を表します)のカードが、端からインセットされて表示されます。マテリアルでは、この形状全体を「コンテナ」といいます(Container という実際のウィジェット クラスと混同しないでください)。

カードは通常、他のカードと一緒にコレクションで表示されます。コレクションとしてグリッド形式でレイアウトしましょう。

複数のカードが画面に表示されている場合は、1 つまたは複数のコレクションにグループ化されます。コレクション内のカードは同一平面上にあります。つまり、カードは互いに 1 つの静止高度を共有します(カードが選択されるかドラッグされる場合を除きますが、ここではそのようなことは行いません)。

カードを増やしてコレクションにする

現在、Card は GridView の children: フィールドのインラインで作成されています。多くのコードがネストされ、読みづらくなっています。これを抜き出して、必要な数だけ空のカードを生成でき、Card のリストを返す関数にしてみましょう。

新しいプライベート関数を build() 関数の上に作成します(アンダースコアで始まる関数はプライベート API です)。

// TODO: Make a collection of cards (102)
List<Card> _buildGridCards(int count) {
  List<Card> cards = List.generate(
    count,
    (int index) => Card(
      clipBehavior: Clip.antiAlias,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          AspectRatio(
            aspectRatio: 18.0 / 11.0,
            child: Image.asset('assets/diamond.png'),
          ),
          Padding(
            padding: EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Text('Title'),
                SizedBox(height: 8.0),
                Text('Secondary Text'),
              ],
            ),
          ),
        ],
      ),
    ),
  );

  return cards;
}

生成されたカードを GridView の children フィールドに割り当てます。必ず、GridView に含まれるものをすべて、この新しいコードに置き換えてください

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(10) // Replace
),

プロジェクトを保存します。

Android

iOS

カードはありますが、まだ何も表示されていません。今度は商品データを追加しましょう。

商品データを追加する

このアプリには、画像、名前、価格を持つ商品が複数あります。これを、すでにカードの中にあるウィジェットに追加しましょう。

次に home.dart で、新しいパッケージと、データモデル用に用意したファイルをインポートします。

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

import 'model/products_repository.dart';
import 'model/product.dart';

最後に、商品情報を取得してそのデータをカードで使用するように、_buildGridCards() を変更します。

// TODO: Make a collection of cards (102)

// Replace this entire method
List<Card> _buildGridCards(BuildContext context) {
  List<Product> products = ProductsRepository.loadProducts(Category.all);

  if (products == null || products.isEmpty) {
    return const <Card>[];
  }

  final ThemeData theme = Theme.of(context);
  final NumberFormat formatter = NumberFormat.simpleCurrency(
      locale: Localizations.localeOf(context).toString());

  return products.map((product) {
    return Card(
      clipBehavior: Clip.antiAlias,
      // TODO: Adjust card heights (103)
      child: Column(
        // TODO: Center items on the card (103)
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          AspectRatio(
            aspectRatio: 18 / 11,
            child: Image.asset(
              product.assetName,
              package: product.assetPackage,
             // TODO: Adjust the box size (102)
            ),
          ),
          Expanded(
            child: Padding(
              padding: EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
               // TODO: Align labels to the bottom and center (103)
               crossAxisAlignment: CrossAxisAlignment.start,
                // TODO: Change innermost Column (103)
                children: <Widget>[
                 // TODO: Handle overflowing labels (103)
                 Text(
                    product.name,
                    style: theme.textTheme.headline6,
                    maxLines: 1,
                  ),
                  SizedBox(height: 8.0),
                  Text(
                    formatter.format(product.price),
                    style: theme.textTheme.subtitle2,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }).toList();
}

注: コンパイルと実行はまだ行いません。変更をもう 1 つ行います。

また、コンパイルしてみる前に、BuildContext_buildGridCards() に渡すように build() 関数を変更します。

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(context) // Changed code
),

Android

iOS

カードの間に縦方向のスペースを加えていないことにお気づきでしょうか。これは、デフォルトでカードの上下に 4 ポイントのパディングがあるためです。

プロジェクトを保存します。

商品データが表示されますが、画像の周囲に余分なスペースがあります。デフォルトで、画像は .scaleDownBoxFit で描画されます(この場合)。これを .fitWidth に変更し、少し拡大して余分な空白を削除します。

fit: フィールドを画像に追加し、その値を BoxFit.fitWidth にします。

  // TODO: Adjust the box size (102)
  fit: BoxFit.fitWidth,

Android

iOS

これで、商品がアプリに完璧に表示されるようになりました。

アプリに、ログイン画面からホーム画面に移動して商品を表示する基本的なフローができました。ほんの数行のコードで、トップ アプリバー(タイトルと 3 つのボタンがある)と、カード(アプリのコンテンツを表示する)を追加しました。ホーム画面は、基本的な構造と実用的なコンテンツを備えた、シンプルかつ機能的なものになりました。

次のステップ

トップ アプリバー、カード、テキスト フィールド、ボタンにより、MDC-Flutter ライブラリの 4 つのコア コンポーネントを使用しました。Flutter ウィジェット カタログには、さらに多くのコンポーネントがあります。

アプリは完全に機能しますが、まだ特定のブランドや視点を表現していません。MDC-103: 色、形状、高度、タイプによるマテリアル デザインのテーマ設定では、これらのコンポーネントのスタイルをカスタマイズして、鮮やかでモダンなブランドを表現します。

次の Codelab

この Codelab を完了するためにそれなりの時間と労力を必要とした

非常にそう思う そう思う どちらとも言えない そう思わない まったくそう思わない

今後もマテリアル コンポーネントを使用したい

非常にそう思う そう思う どちらとも言えない そう思わない まったくそう思わない