Flutter を使用した美しい UI の作成

Flutter は、1 つのコードベースからネイティブにコンパイルして、モバイル、ウェブ、デスクトップの美しいアプリケーションを作成できる Google の UI ツールキットです。この Codelab では、Android 用、iOS 用、ウェブ用(任意)のシンプルなチャット アプリケーションを作成します。

この Codelab では、初めての Flutter アプリの作成、パート 1パート 2 よりも、さらに詳しく Flutter について説明します。Flutter の基本を学びたい方は、こちらから始めてください。

学習内容

  • Android と iOS の両方で自然に見える Flutter アプリの作成方法
  • Android Studio IDE の使用方法(Android Studio と IntelliJ の Flutter プラグインでサポートされている多数のショートカットを使用)
  • Flutter アプリのデバッグ方法
  • エミュレータ、シミュレータ、デバイスで Flutter アプリを実行する方法

この Codelab で学びたいことは次のどれですか?

このトピックは初めてなので、簡単に概要を知りたい。 このトピックについてある程度は知っているが、復習したい。プロジェクトで使用するサンプルコードを確認したい。特定の項目に関する説明を確認したい。

この Codelab を完了するには、Flutter SDK(ダウンロード)とエディタ(構成)の 2 つのソフトウェアが必要です。この Codelab では Android Studio を使用することを前提としていますが、別のエディタを使用しても構いません。

この Codelab は、次のデバイスのどれを使用しても実行できます。

  • パソコンに接続され、デベロッパー モードに設定されている実機(Android または iOS)
  • Android Emulator
  • iOS シミュレータ
  • Chrome ブラウザ
  • Windows、macOS、または Linux のデスクトップ(Flutter のデスクトップ サポートを有効にしている場合)

Android で実行する場合、Android Studio でセットアップを行う必要があります。iOS で実行する場合、Mac に Xcode もインストールしておく必要があります。詳細については、「エディタの設定」をご覧ください。

テンプレート化された簡単な Flutter アプリを作成し、このスターター アプリを修正して最終的なアプリを完成させます。

b2f84ff91b0e1396.png Android Studio を起動します。

  1. 開いているプロジェクトがない場合は、スタートページで [Start a new Flutter app] を選択します。それ以外の場合は、[File] > [New] > [New Flutter Project] を選択します。
  2. プロジェクトの種類として [Flutter Application] を選択し、[Next] をクリックします。
  3. Flutter SDK のパスに SDK の場所が指定されていることを確認します(テキスト フィールドが空欄の場合は、[Install SDK] を選択します)。
  4. プロジェクト名として「friendly_chat」と入力し、[Next] をクリックします。
  5. Android Studio に推奨されたデフォルトのパッケージ名を使用し、[Next] をクリックします。
  6. [Finish] をクリックします。
  7. Android Studio により SDK がインストールされ、プロジェクトが作成されるまで待ちます。

b2f84ff91b0e1396.png コマンドラインで Flutter アプリを作成することもできます。

$ flutter create friendly_chat
$ cd friendly_chat
$ dart migrate --apply-changes
$ flutter run

トラブルシューティング

テンプレート化された簡単なアプリの作成の詳細については、テストドライブについてのページをご覧ください。または、次のリンクのコードを確認してから、続きに進んでください。

このセクションでは、デフォルトのサンプルアプリを修正して、チャットアプリを作成します。Flutter を使用して、以下の機能を備えた拡張可能でシンプルなチャットアプリである FriendlyChat を作成するのが目標です。

  • アプリには、テキスト メッセージがリアルタイムで表示されます。
  • テキスト文字列のメッセージを入力して、Return キーまたは [Send] ボタンを押すと送信できます。
  • UI は、Android デバイス、iOS デバイス、ウェブで動作します。

最終版のアプリを DartPad で試すことができます

メインアプリのスキャフォールドを作成する

最初に追加する要素は、アプリのタイトルを静的に表示するシンプルなアプリバーです。この Codelab の以降のセクションでは、レスポンシブでステートフルな UI 要素を少しずつ追加します。

main.dart ファイルは Flutter プロジェクトの lib ディレクトリにあり、このファイルにアプリの実行を開始する main() 関数が入っています。

b2f84ff91b0e1396.png main.dart 内のコードをすべて次のコードに置き換えます。

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      title: 'FriendlyChat',
      home: Scaffold(
        appBar: AppBar(
          title: Text('FriendlyChat'),
        ),
      ),
    ),
  );
}

cf1e10b838bf60ee.png 確認内容

  • Dart プログラムは、コマンドライン アプリ、AngularDart アプリ、Flutter アプリのいずれでも、main() 関数で始まります。
  • main() 関数と runApp() 関数の定義は、自動生成されたアプリと同じです。
  • runApp() 関数は引数として Widget を受け取ります。これは、起動時に Flutter フレームワークによって展開され画面に表示されます。
  • このチャットアプリでは UI にマテリアル デザインの要素を使用するため、MaterialApp オブジェクトが作成され、runApp() 関数に渡されます。MaterialApp ウィジェットがアプリのウィジェット ツリーのルートになります。
  • home 引数では、アプリ内で表示されるデフォルトの画面を指定します。これは、子ウィジェットにシンプルな AppBar を持つ Scaffold ウィジェットで構成されています。マテリアル アプリとしては一般的です。

b2f84ff91b0e1396.png エディタで実行アイコン 6869d41b089cc745.png をクリックしてアプリを実行します。初めてアプリを実行する場合、時間がかかることがありますが、以降のステップでは高速化されます。

febbb7a3b70462b7.png

次のように表示されます:

Pixel 3XL

iPhone 11

チャット画面を作成する

インタラクティブなコンポーネントの土台として、シンプルなアプリを、変化しないルートレベルの FriendlyChatApp ウィジェットと、メッセージが届いたときと内部状態が変化したときに再構築される子 ChatScreen ウィジェットという、ウィジェットのサブクラス 2 つに分割します。この段階では、どちらのクラスも StatelessWidget からの拡張で問題ありませんが、ChatScreen は後でステートフル ウィジェットに変更します。こうすることで、必要に応じて状態を変更できるようになります。

b2f84ff91b0e1396.png FriendlyChatApp ウィジェットを作成します。

  1. main() の中の MaterialApp の「M」の前にカーソルを置きます。
  2. 右クリックして、[Refactor] > [Extract] > [Extract Flutter widget] を選択します。

a133a9648f86738.png

  1. [ExtractWidget] ダイアログに「FriendlyChatApp」と入力し、[Refactor] ボタンをクリックします。MaterialApp コードは FriendlyChatApp という新しいステートレス ウィジェットに配置され、main() は、runApp() 関数を呼び出したときにこのクラスを呼び出すように更新されます。
  2. home: の後のテキスト ブロックを選択します。「Scaffold(」で始め、末尾に Scaffold の閉じかっこ「)」を付けます。末尾のカンマは付けません。
  3. ChatScreen,」と入力し、ポップアップで [ChatScreen()] を選択します(黄色の丸の中に等号が表示されている ChatScreen エントリを選択します。定数ではなく空のかっこでクラスが作成されます)。

b2f84ff91b0e1396.png ステートレス ウィジェット ChatScreen を作成します。

  1. FriendlyChatApp クラスの下の 27 行目付近で、「stless」と入力します。エディタから、Stateless ウィジェットを作成するかどうかを尋ねられます。Return キーを押して確定します。ボイラープレート コードが挿入され、カーソルがステートレス ウィジェットの名前を入力する位置に移動します。
  2. ChatScreen」と入力します。

b2f84ff91b0e1396.png ChatScreen ウィジェットを更新します。

  1. ChatScreen ウィジェットで、コンテナを選択し、「Scaffold」と入力します。ポップアップで [Scaffold] を選択します。
  2. カーソルはかっこの中に置く必要があります。Return キーを押して改行します。
  3. appBar,」と入力し、ポップアップで [appBar:] を選択します。
  4. appBar: の後に AppBar, と入力し、ポップアップで AppBar クラスを選択します。
  5. かっこの中に「title,」と入力し、ポップアップで [title:] を選択します。
  6. title: の後に「Text,」と入力し、Text クラスを選択します。
  7. Text のボイラープレート コードに data という単語が含まれています。data の後の最初のカンマを削除します。data, を選択し、'FriendlyChat' で置き換えます(Dart では、単一引用符と二重引用符を使用できますが、テキストに単一引用符が含まれている場合以外は、単一引用符を使うことをおすすめします)。

コードペインの右上を確認します。緑色のチェックマークが表示されていたら、コードの分析結果は合格です。これで完了です

cf1e10b838bf60ee.png 確認内容

このステップでは、Flutter フレームワークの重要なコンセプトを紹介しています。

  • ユーザー インターフェースの一部を、ウィジェットの build() メソッドで記述しています。FriendlyChatApp ウィジェットと ChatScreen ウィジェットがウィジェット階層に挿入されたときと、それらの依存関係が変更されたときに、ウィジェットの build() メソッドがフレームワークによって呼び出されます。
  • @override は、タグ付けされたメソッドがスーパークラスのメソッドをオーバーライドすることを示す Dart アノテーションです。
  • ScaffoldAppBar などの一部のウィジェットは、マテリアル デザイン アプリに固有のものです。Text などの他のウィジェットは汎用であり、どのアプリでも使用できます。Flutter フレームワーク内のライブラリのウィジェット間には互換性があり、1 つのアプリで同時に使用できます。
  • main() メソッドを簡略化すると、ホットリロードで main() が実行されないため、ホットリロードが可能になります。

b2f84ff91b0e1396.png ホットリロード ボタン 48583acd5d1a5e12.png をクリックすると、変更がすぐに表示されます。UI を別々のクラスに分割してルート ウィジェットを変更しても、UI に目に見える変化はありません。

トラブルシューティング

アプリが正常に動作しない場合は、入力ミスを探してください。必要に応じて、次のリンクのコードを確認してから、先に進んでください。

このセクションでは、ユーザーによるチャット メッセージの入力と送信を可能にするユーザー コントロールの作成方法を説明します。

64fd9c97437a7461.png

デバイスでテキスト フィールドをクリックすると、ソフト キーボードが表示されます。空でない文字列を入力し、ソフト キーボードの Return キーを押して、チャット メッセージを送信できます。または、入力フィールドの横にある送信アイコンボタンを押しても、入力したテキスト メッセージを送信できます。

現時点では、メッセージ作成用の UI はチャット画面の上部に表示されますが、次のステップでメッセージを表示する UI を追加すると、チャット画面の下部に移動します。

インタラクティブなテキスト入力フィールドを追加する

Flutter フレームワークには、TextField というマテリアル デザイン ウィジェットが用意されています。これは入力フィールドの動作をカスタマイズするプロパティを備えた StatefulWidget(変更可能な状態を持つウィジェット)です。State は、ウィジェットが構築されたときに同期的に読み取ることができ、ウィジェットの存続期間中に変わる可能性がある情報です。FriendlyChat アプリに初めてステートフル ウィジェットを追加するときには、いくつかの変更が必要です。

b2f84ff91b0e1396.png ChatScreen クラスをステートフルに変更します。

  1. class ChatScreen extends StatelessWidget の行の ChatScreen を選択します。
  2. Option+Return(macOS)または Alt+Enter(Linux と Windows)を押してメニューを表示します。
  3. メニューから [Convert to StatefulWidget] を選択します。このクラスは、状態を管理するための新しい _ChatScreenState クラスを含むステートフル ウィジェットのボイラープレート コードで自動的に更新されます。

テキスト フィールドの操作を管理するには、TextEditingController オブジェクトを使用して、入力フィールドの内容を読み取り、チャット メッセージの送信後にフィールドをクリアします。

b2f84ff91b0e1396.png TextEditingController_ChatScreenState. に追加する

次の行を _ChatScreenState クラスの最初の行として追加します。

final _textController = TextEditingController();

ここまでで、アプリで状態を管理できるようになったので、入力フィールドと送信ボタンを備えた _ChatScreenState クラスを作成できます。

b2f84ff91b0e1396.png _buildTextComposer 関数を _ChatScreenState に追加します。

  Widget _buildTextComposer() {
    return  Container(
        margin: EdgeInsets.symmetric(horizontal: 8.0),
      child: TextField(
        controller: _textController,
        onSubmitted: _handleSubmitted,
        decoration: InputDecoration.collapsed(
            hintText: 'Send a message'),
      ),
    );
  }

cf1e10b838bf60ee.png 確認内容

  • Flutter では、ウィジェットのステートフル データは State オブジェクト内にカプセル化されます。そして、State オブジェクトは、StatefulWidget クラスを拡張するウィジェットに関連付けられます。
  • 上記のコードでは、設定済みの TextField ウィジェットを含む Container ウィジェットを返す _buildTextComposer() という非公開メソッドを定義しています。
  • Container ウィジェットにより、画面の端と入力フィールドの両端の間に水平マージンが追加されます。
  • EdgeInsets.symmetric に渡される単位は、デバイスのピクセル比に応じて物理ピクセル数に変換される論理ピクセル数です。Android の「密度非依存ピクセル」、iOS の「ポイント」に相当します。
  • onSubmitted プロパティには、非公開コールバック メソッドである _handleSubmitted() が設定されています。このメソッドは、当初はこのフィールドをクリアするだけですが、後でチャット メッセージを送信するように拡張します。
  • TextEditingController を備えた TextField を使用すると、テキスト フィールドを制御できます。このコントローラはフィールドをクリアし、その値を読み取ります。

b2f84ff91b0e1396.png テキスト コントローラをクリアするために、_handleSubmitted 関数を _ChatScreenState に追加します。

  void _handleSubmitted(String text) {
    _textController.clear();
  }

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

b2f84ff91b0e1396.png _ChatScreenState.build() メソッドを更新します。

appBar: AppBar(...) の行の後に、body: プロパティを追加します。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('FriendlyChat')),
      body: _buildTextComposer(),    // NEW
    );
  }

cf1e10b838bf60ee.png 確認内容

  • _buildTextComposer メソッドは、テキスト入力フィールドをカプセル化するウィジェットを返します。
  • _buildTextComposerbody プロパティに追加すると、アプリにテキスト入力ユーザー コントロールが表示されます。

b2f84ff91b0e1396.png アプリをホットリロードします。次のような画面が表示されます。

Pixel 3XL

iPhone 11

レスポンシブな送信ボタンを追加する

次は、テキスト フィールドの右側に送信ボタンを追加します。ここでは、レイアウトに構造を追加する必要があります。

b2f84ff91b0e1396.png _buildTextComposer 関数で、TextFieldRow: でラップします。

  1. _buildTextComposerTextField を選択します。
  2. Option+Return(macOS)または Alt+Enter(Linux と Windows)を押してメニューを表示し、[Wrap with widget] を選択します。TextField をラップする新しいウィジェットが追加されます。プレースホルダ名が選択され、IDE で新しいプレースホルダ名を入力できる状態になります。
  3. Row,」と入力し、表示されるリストから [Row] を選択します。Row のコンストラクタの定義を含むポップアップが表示されます。child プロパティに赤い枠線が付けられ、必要な children プロパティが指定されていないという指摘がアナライザに表示されます。
  4. child にカーソルを合わせると、ポップアップが表示されます。ポップアップで、プロパティを children に変更するかどうかを尋ねられます。変更することを選択します。
  5. children プロパティには、ウィジェットを 1 つではなく、リストで設定します(現時点でリスト内の項目は 1 つだけですが、後で追加します)。children: というテキストの後に左角かっこ([)を入力して、項目が 1 つのリストにウィジェットを変換します。エディタは右閉じかっこも追加しますが、この閉じかっこは削除します。数行下に移動して、Row を閉じる右かっこの直前に、右かっことカンマ(],)を入力します。アナライザに緑色のチェックマークが表示されるはずです。
  6. これで正しいコードになりましたが、形は整っていません。コードペインで右クリックし、[Reformat Code with dartfmt] を選択します。

b2f84ff91b0e1396.png TextFieldFlexible でラップします。

  1. [Row] を選択します。
  2. Option+Return(macOS)または Alt+Enter(Linux と Windows)を押してメニューを表示し、[Wrap with widget] を選択します。TextField をラップする新しいウィジェットが追加されます。プレースホルダ名が選択され、IDE で新しいプレースホルダ名を入力できる状態になります。
  3. Flexible,」と入力し、表示されるリストから [Flexible] を選択します。Row のコンストラクタの定義を含むポップアップが表示されます。
Widget _buildTextComposer() {
  return  Container(
    margin: EdgeInsets.symmetric(horizontal: 8.0),
    child:  Row(                             // NEW
      children: [                            // NEW
         Flexible(                           // NEW
          child:  TextField(
            controller: _textController,
            onSubmitted: _handleSubmitted,
            decoration:  InputDecoration.collapsed(
                hintText: 'Send a message'),
          ),
        ),                                    // NEW
      ],                                      // NEW
    ),                                        // NEW
  );
}

cf1e10b838bf60ee.png 確認内容

  • Row を使用することで、入力フィールドの横に送信ボタンを配置しています。
  • TextFieldFlexible ウィジェットでラップすることで、Row が自動的にテキスト フィールドのサイズを調整して、ボタンで使用されない余白を使うようにしています。
  • 右角かっこの後にカンマを追加することで、フォーマッタにコードの整形方法を認識させています。

次は、送信ボタンを追加します。マテリアル アプリなので、対応するマテリアル アイコン 2de111ba4b057a1e.png を使用してください。

b2f84ff91b0e1396.png 送信ボタンを Row に追加します。

送信ボタンが Row のリストの 2 番目の項目になります。

  1. Flexible ウィジェットの右角かっことカンマの最後にカーソルを置き、Return キーを押して改行します。
  2. Container,」と入力し、ポップアップから [Container] を選択します。カーソルはコンテナのかっこ内に移動します。Return キーを押して改行します。
  3. 次のコードをコンテナに追加します。
margin: EdgeInsets.symmetric(horizontal: 4.0),
child: IconButton(
    icon: const Icon(Icons.send),
    onPressed: () => _handleSubmitted(_textController.text)),

cf1e10b838bf60ee.png 確認内容

  • IconButton送信ボタンが表示されます。
  • icon プロパティにマテリアル ライブラリの Icons.send 定数を指定することで、新しい Icon インスタンスを作成しています。
  • IconButtonContainer ウィジェットに入れることで、ボタンの余白をカスタマイズ可能にして、入力フィールドの隣の適切な位置に表示されるようにしています。
  • onPressed プロパティでは、匿名関数を使用して _handleSubmitted() メソッドを呼び出し、_textController を使用してメッセージの内容を渡しています。
  • Dart では、関数を宣言するときに矢印の構文(=> expression)が使用されることがあります。これは { return expression; } の省略形であり、1 行からなる関数にのみ使用されます。匿名関数やネストされた関数など、Dart 関数サポートの概要については、Dart 言語ツアーをご覧ください。

b2f84ff91b0e1396.png アプリをホットリロードすると、送信ボタンが表示されます。

Pixel 3XL

iPhone 11

ボタンの色は黒です。これはデフォルトのマテリアル デザインのテーマの色です。アプリのアイコンにアクセント カラーを付けるには、color 引数を IconButton に渡すか、別のテーマを適用します。

b2f84ff91b0e1396.png _buildTextComposer()ContainerIconTheme: でラップします。

  1. _buildTextComposer() 関数の上部にある Container を選択します。
  2. Option+Return(macOS)または Alt+Enter(Linux と Windows)を押してメニューを表示し、[Wrap with widget] を選択します。Container をラップする新しいウィジェットが追加されます。プレースホルダ名が選択され、IDE で新しいプレースホルダ名を入力できる状態になります。
  3. IconTheme,」と入力し、リストから [IconTheme] を選択します。child プロパティは赤い枠で囲まれ、data プロパティが必須だという指摘がアナライザに表示されます。
  4. 次のように data プロパティを追加します。
return IconTheme(
  data: IconThemeData(color: Theme.of(context).accentColor), // NEW
  child: Container(

cf1e10b838bf60ee.png 確認内容

  • アイコンは、色、透明度、サイズを IconTheme ウィジェットから継承し、これらの特性の定義には IconThemeData オブジェクトが使用されています。
  • IconThemedata プロパティは、現在のテーマの ThemeData オブジェクトを指定しています。これにより、ボタン(およびウィジェット ツリーのこの部分にある他のアイコン)に、現在のテーマのアクセント カラーが設定されます。
  • BuildContext オブジェクトは、アプリのウィジェット ツリー内でウィジェットの場所を表すハンドルです。各ウィジェットには独自の BuildContext があり、これが StatelessWidget.build 関数または State.build 関数によって返されるウィジェットの親になります。つまり、_buildTextComposer() は、それをカプセル化している State オブジェクトから BuildContext オブジェクトにアクセスできます。明示的にコンテキストをメソッドに渡す必要はありません。

b2f84ff91b0e1396.png アプリをホットリロードします。送信ボタンが青色に変わります。

Pixel 3XL

iPhone 11

トラブルシューティング

アプリが正常に動作しない場合は、入力ミスを探してください。必要に応じて、次のリンクのコードを確認してから、先に進んでください。

e57d18c5bb8f2ac7.png 面白いものが見つかりました!

アプリのデバッグには複数の方法があります。IDE を直接使用してブレークポイントを設定することも、Dart DevTools(Chrome DevTools ではありません)を使用することもできます。この Codelab では、Android Studio と IntelliJ を使用してブレークポイントを設定する方法を説明します。別のエディタ(VS Code など)を使用している場合は、DevTools を使用してデバッグします。Dart DevTools の簡単な概要については、ウェブで初めての Flutter アプリを作成するの Step 2.5 をご覧ください。

Android Studio と IntelliJ IDE を使用すると、エミュレータ、シミュレータ、またはデバイスで実行されている Flutter アプリのデバッグができます。これらのエディタでは、次のことができます。

  • アプリをデバッグするデバイスまたはシミュレータを選択する。
  • コンソール メッセージを表示する。
  • コードにブレークポイントを設定する。
  • 実行時に変数や式を検証する。

Android Studio と IntelliJ のエディタでは、アプリの実行中にシステムログを表示し、Debugger UI でブレークポイントを利用して実行フローを制御できます。

6ea611ca007eb43c.png

ブレークポイントを利用する

b2f84ff91b0e1396.png ブレークポイントを使用して Flutter アプリをデバッグします。

  1. ブレークポイントを設定するソースファイルを開きます。
  2. ブレークポイントを設定する行をクリックし、[Run] > [Toggle Line Breakpoint] を選択します。または、行番号の右側にあるガターをクリックして、ブレークポイントを切り替えることもできます。
  3. デバッグモードで実行していない場合は、アプリを停止します。
  4. [Run] > [Debug] を使用するか、UI でデバッグ実行ボタンをクリックして、アプリを再起動します。

エディタで Debugger UI が起動し、ブレークポイントに到達するとアプリの実行が一時停止します。そうすると、Debugger の UI にあるコントロールを使用して、エラーの原因を特定できます。

デバッガの使い方を練習するために、FriendlyChat アプリの build() メソッドにブレークポイントを設定し、アプリを実行してデバッグします。スタック フレームを調べると、アプリでのメソッド呼び出しの履歴を確認できます。

基本アプリのスキャフォールドと画面ができたので、次はチャット メッセージを表示する領域を定義しましょう。

de23b9bb7bf84592.png

チャット メッセージ リストを実装する

このセクションでは、コンポジションを使用してチャット メッセージを表示するウィジェットを作成します(複数の小さなウィジェットを作成して組み合わせます)。まず、1 つのチャット メッセージを表すウィジェットから始めます。次に、そのウィジェットを親となるスクロール可能なリストにネストします。最後に、スクロール可能なリストを基本アプリのスキャフォールド内にネストします。

b2f84ff91b0e1396.png ChatMessage ステートレス ウィジェットを追加します。

  1. FriendlyChatApp クラスの後にカーソルを置き、stless を入力します(クラスの順序は重要ではありませんが、この順番の方が自分のコードと解答との比較が簡単になります)。
  2. クラス名に「ChatMessage」と入力します。

b2f84ff91b0e1396.png RowChatMessagebuild() メソッドに追加します。

  1. return Container() のかっこ内にカーソルを置き、Return キーを押して改行します。
  2. 次のように margin プロパティを追加します。
margin: EdgeInsets.symmetric(vertical: 10.0),
  1. Container' の子は Row になります。Row のリストには、アバターとテキスト列の 2 つのウィジェットが含まれています。
return Container(
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Container(
        margin: const EdgeInsets.only(right: 16.0),
        child: CircleAvatar(child: Text(_name[0])),
      ),
      Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(_name, style: Theme.of(context).textTheme.headline4),
          Container(
            margin: EdgeInsets.only(top: 5.0),
            child: Text(text),
          ),
        ],
      ),
    ],
  ),
);
  1. text 変数とコンストラクタを ChatMessage の先頭に追加します。
class ChatMessage extends StatelessWidget {
  ChatMessage({required this.text}); // NEW
  final String text;                 // NEW

この時点では、アナライザに _name が未定義だという指摘のみが表示されているはずです。これを修正します。

b2f84ff91b0e1396.png _name 変数を定義します。

次のように _name 変数を定義します。Your Name は自分の名前に置き換えます。この変数を使用して、各チャット メッセージに送信者名のラベルを付けます。この Codelab では、値をハードコードしていますが、ほとんどのアプリは認証によって送信者名を取得します。main() 関数の後に、次の行を追加します。

String _name = 'Your Name';

cf1e10b838bf60ee.png 確認内容

  • ChatMessagebuild() メソッドは、チャット メッセージを送信したユーザーを表すシンプルでグラフィカルなアバターを表示する Row を返します。Column ウィジェットには、送信者名とメッセージの本文が含まれます。
  • CircleAvatar には、_name 変数の値の最初の文字を子 Text ウィジェットに渡して、ユーザー名のイニシャルを付けることで、個人を区別できるようにします。
  • crossAxisAlignment パラメータは、Row コンストラクタで CrossAxisAlignment.start を指定して、親ウィジェットに対するアバターとメッセージの位置を決めます。アバターについては、親はメイン軸が水平である Row ウィジェットであるため、CrossAxisAlignment.start により垂直軸に沿って最も高い位置に配置されます。メッセージについては、親はメイン軸が垂直である Column ウィジェットであるため、テキストは CrossAxisAlignment.start により水平軸に沿って最も左に配置されます。
  • アバターの隣に 2 つの Text ウィジェットが垂直方向に並び、上に送信者名と下にメッセージの本文が表示されます。
  • Theme.of(context) により、アプリにデフォルトの Flutter ThemeData オブジェクトが設定されます。後のステップで、Android と iOS で異なるスタイルがアプリに設定されるように、このデフォルト テーマをオーバーライドします。
  • ThemeDatatextTheme プロパティから、headline4 のようなテキスト用のマテリアル デザインの論理スタイルを利用できるため、フォントサイズなどのテキスト属性をハードコードする必要がなくなります。この例では、送信者名のスタイルがメッセージ テキストよりも大きくなっています。

b2f84ff91b0e1396.png アプリをホットリロードします。

テキスト フィールドにメッセージを入力します。送信ボタンを押すと、メッセージがクリアされます。テキスト フィールドに長文のメッセージを入力すると、テキスト フィールドがいっぱいになったときに何が起こるかを確認できます。後のステップ 9 で、Expanded ウィジェットで列をラップして Text ウィジェットが折り返すようにします。

UI にチャット メッセージ リストを実装する

次の改良は、チャット メッセージのリストを取得して UI に表示することです。ユーザーがメッセージ履歴を確認できるように、このリストをスクロール可能にする必要があります。このリストでは、メッセージが時系列順に表示され、最新のメッセージが画面上のリストの最下部に表示されるようにします。

b2f84ff91b0e1396.png _messages リストを _ChatScreenState に追加します。

_ChatScreenState の定義で、各チャット メッセージを表す _messages という List メンバーを追加します。

class _ChatScreenState extends State<ChatScreen> {
  final List<ChatMessage> _messages = [];      // NEW
  final _textController = TextEditingController();

b2f84ff91b0e1396.png _ChatScreenState._handleSubmitted() メソッドを変更します。

テキスト フィールドからチャット メッセージを送信したとき、アプリではメッセージ リストに新しいメッセージを追加する必要があります。_handleSubmitted() メソッドを変更して、この動作を実現します。

void _handleSubmitted(String text) {
  _textController.clear();
  ChatMessage message = ChatMessage(    //NEW
    text: text,                         //NEW
  );                                    //NEW
  setState(() {                         //NEW
    _messages.insert(0, message);       //NEW
  });                                   //NEW
 }

b2f84ff91b0e1396.png 内容を送信した後、フォーカスをテキスト フィールドに戻します。

  1. FocusNode_ChatScreenState に追加します。
class _ChatScreenState extends State<ChatScreen> {
  final List<ChatMessage> _messages = [];
  final _textController = TextEditingController();
  final FocusNode _focusNode = FocusNode();    // NEW
  1. focusNode プロパティを _buildTextComposer()TextField に追加します。
child: TextField(
  controller: _textController,
  onSubmitted: _handleSubmitted,
  decoration: InputDecoration.collapsed(hintText: 'Send a message'),
  focusNode: _focusNode,  // NEW
),
  1. _handleSubmitted() で、setState() を呼び出した後に、TextField にフォーカスを要求します。
    setState(() {
      _messages.insert(0, message);
    });
    _focusNode.requestFocus();  // NEW

cf1e10b838bf60ee.png 確認内容

  • リスト内の各項目は ChatMessage インスタンスです。
  • リストは空に初期化されます。
  • setState() を呼び出して _messages を変更すると、ウィジェット ツリーのこの部分が変更され、UI を再作成する必要があることをフレームワークが認識します。setState() では同期オペレーションのみを実行するようにしてください。そうしなかった場合、オペレーションが完了する前にフレームワークがウィジェットを再構築してしまうためです。
  • 一般に、このメソッド呼び出しの外で非公開データが変更された後に、空のクロージャを与えて setState() を呼び出すこともできます。しかし、後で呼び出すことを忘れないように、setState のクロージャの中でデータを更新することをおすすめします。

b2f84ff91b0e1396.png アプリをホットリロードします。

テキスト フィールドにテキストを入力し、Return キーを押します。再度テキスト フィールドにフォーカスが移動します。

メッセージ リストを配置する

チャット メッセージのリストを表示する準備ができました。_messages リストから ChatMessage ウィジェットを取得し、スクロール可能なリスト用に ListView ウィジェットに入れます。

b2f84ff91b0e1396.png _ChatScreenStatebuild() メソッドで、Column 内に ListView を追加します。

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text ('FriendlyChat')),
    body: Column(                                            // MODIFIED
      children: [                                            // NEW
        Flexible(                                            // NEW
          child: ListView.builder(                           // NEW
            padding: EdgeInsets.all(8.0),                    // NEW
            reverse: true,                                   // NEW
            itemBuilder: (_, int index) => _messages[index], // NEW
            itemCount: _messages.length,                     // NEW
          ),                                                 // NEW
        ),                                                   // NEW
        Divider(height: 1.0),                                // NEW
        Container(                                           // NEW
          decoration: BoxDecoration(
            color: Theme.of(context).cardColor),             // NEW
          child: _buildTextComposer(),                       // MODIFIED
        ),                                                   // NEW
      ],                                                     // NEW
    ),                                                       // NEW
  );
}

cf1e10b838bf60ee.png 確認内容

  • リスト内の項目ごとに 1 回呼び出される関数を提供することで、ListView.builder ファクトリ メソッドによりオンデマンドでリストが作成されます。この関数は、呼び出されるごとに新しいウィジェットを返します。builder は、children パラメータの変更を自動的に検出し、再作成を開始します。
  • ListView.builder コンストラクタに渡されるパラメータにより、リストの内容と外観がカスタマイズされます。
  • padding により、メッセージ テキストの周囲に余白が作られます。
  • itemCount は、リスト内のメッセージ数を指定します。
  • itemBuilder は、各ウィジェットを [index] に作成する関数を提供します。現在のビルド コンテキストは必要ないため、IndexedWidgetBuilder の最初の引数は無視できます。名前がアンダースコア(_)だけの引数は、その引数が使用されないことを表します。
  • Scaffold ウィジェットの body プロパティに、受信メッセージのリスト、入力フィールド、送信ボタンを追加しました。このレイアウトでは、次のウィジェットを使用します。
  • Column: 直接の子を垂直にレイアウトします。Column ウィジェットには、スクロール リストおよび入力フィールドの行となる子ウィジェット(Row と同じ)のリストが入ります。
  • FlexibleListView の親): 受信メッセージのリストを Column の高さに合わせて広げ、TextField は固定サイズのままにするようフレームワークに指示します。
  • Divider: メッセージを表示する UI と、メッセージ作成用のテキスト入力フィールドの間に、水平の線を描画します。
  • Container(テキスト作成の親): 背景画像、パディング、マージン、その他の一般的なレイアウトの詳細を定義します。
  • decoration: 背景色を定義する新しい BoxDecoration オブジェクトを作成します。この例では、デフォルト テーマの ThemeData オブジェクトによって定義された cardColor を使用しています。これにより、メッセージ リストとは別の背景がメッセージ作成の UI に設定されます。

b2f84ff91b0e1396.png アプリをホットリロードします。次のような画面が表示されます。

Pixel 3XL

iPhone 11

b2f84ff91b0e1396.png 作成用の UI を使用してチャット メッセージを送信し、構築したものを表示してみましょう。

Pixel 3XL

iPhone 11

トラブルシューティング

アプリが正常に動作しない場合は、入力ミスを探してください。必要に応じて、次のリンクのコードを確認してから、先に進んでください。

ウィジェットにアニメーションを追加すると、アプリのユーザー エクスペリエンスがスムーズで直感的になります。このセクションでは、チャット メッセージ リストに基本的なアニメーション効果を追加する方法を説明します。

ユーザーが新しいチャット メッセージを送信する際には、単にメッセージ リストに表示するのではなく、メッセージが画面下部から垂直にイーズアップするアニメーションを付けます。

Flutter のアニメーションは、型付きの値とステータス(forwardreversecompleteddismissed など)を含んだ Animation にカプセル化されています。アニメーション オブジェクトをウィジェットにアタッチしたり、アニメーション オブジェクトの変更をリッスンしたりできます。フレームワークは、アニメーション オブジェクトのプロパティの変更に基づいて、ウィジェットの表示方法を変更し、ウィジェット ツリーを再構築できます。

アニメーション コントローラを指定する

AnimationController クラスを使用して、アニメーションの動作を指定します。AnimationController を使用すると、再生時間や再生方向(早送りまたは巻き戻し)などのアニメーションの特性を定義できます。

b2f84ff91b0e1396.png _ChatScreenState クラスの定義を更新して、TickerProviderStateMixin を取り込みます。

class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {   // MODIFIED
  final List<ChatMessage> _messages = [];
  final _textController = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  ...

b2f84ff91b0e1396.png ChatMessage クラスの定義に、アニメーション コントローラを保存する変数を追加します。

class ChatMessage extends StatelessWidget {
  ChatMessage({required this.text, required this.animationController}); // MODIFIED
  final String text;
  final AnimationController animationController;      // NEW
  ...

b2f84ff91b0e1396.png アニメーション コントローラを _handleSubmitted() メソッドに追加します。

void _handleSubmitted(String text) {
  _textController.clear();
  var message = ChatMessage(
    text: text,
    animationController: AnimationController(      // NEW
      duration: const Duration(milliseconds: 700), // NEW
      vsync: this,                                 // NEW
    ),                                             // NEW
  );                                               // NEW
  setState(() {
    _messages.insert(0, message);
  });
  _focusNode.requestFocus();
  message.animationController.forward();           // NEW
}

cf1e10b838bf60ee.png 確認内容

  • AnimationController では、アニメーションの動作時間を 700 ミリ秒に指定しています(時間が長いのでアニメーション効果が遅くなり、遷移が少しずつ進むようになります。実際にアプリを実行する際には、短い時間に設定することをおすすめします)。
  • アニメーション コントローラは、新しい ChatMessage インスタンスにアタッチされ、チャットリストにメッセージが追加されるたびにアニメーションが再生されるように指定しています。
  • AnimationController を作成する際には、vsync 引数を渡す必要があります。vsync は、アニメーションを進めるハートビート(Ticker)の供給源です。この例では、_ChatScreenStatevsync として使用するため、TickerProviderStateMixin ミックスインを _ChatScreenState クラスの定義に追加します。
  • Dart では、ミックスインを使用することで、クラス本体を複数のクラス階層で再利用できます。詳しくは、Dart 言語ツアーの「クラスへの機能の追加: ミックスイン」をご覧ください。

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

このアニメーションに SizeTransition ウィジェットを追加すると、テキストがスライドインしながら徐々に現れるというアニメーション効果が ClipRect に適用されます。

b2f84ff91b0e1396.png SizeTransition ウィジェットを ChatMessagebuild() メソッドに追加します。

  1. ChatMessagebuild() メソッドで、最初の Container インスタンスを選択します。
  2. Option+Return(macOS)または Alt+Enter(Linux と Windows)を押してメニューを表示し、[Wrap with widget] を選択します。
  3. SizeTransition」と入力します。child: プロパティの周囲に赤い枠が表示されます。これは、ウィジェット クラスに必要なプロパティがないことを示しています。SizeTransition, にカーソルを合わせると、sizeFactor が必要であることを指摘し、その作成を提案するツールチップが表示されます。提案を受け入れると、null 値のプロパティが追加されます。
  4. null をインスタンス CurvedAnimation で置き換えます。それにより、2 つのプロパティ(parent(必須)と curve)にボイラープレート コードが追加されます。
  5. parent プロパティの nullanimationController に置き換えます。
  6. curve プロパティの nullCurves.easeOutCurves クラスの定数)に置き換えます。
  7. sizeFactor の後に(同じレベルで)行を追加し、axisAlignment プロパティを SizeTransition に追加し、その値を 0.0 にします。
@override
Widget build(BuildContext context) {
  return SizeTransition(             // NEW
    sizeFactor:                      // NEW
        CurvedAnimation(parent: animationController, curve: Curves.easeOut),  // NEW
    axisAlignment: 0.0,              // NEW
    child: Container(                // MODIFIED
    ...

cf1e10b838bf60ee.png 確認内容

  • CurvedAnimation オブジェクトを SizeTransition クラスと組み合わせて、イーズアウト アニメーション効果を生成しています。イーズアウト効果を使用すると、メッセージがスライドアップしますが、最初は素早く、終わりに近付くにつれて徐々にゆっくりになります。
  • SizeTransition ウィジェットは、テキストがスライドインしながら徐々に現れるというアニメーション効果が付いた ClipRect として動作します。

アニメーションを破棄する

不要になったアニメーション コントローラは破棄して、リソースを解放することをおすすめします。

b2f84ff91b0e1396.png dispose() メソッドを _ChatScreenState. に追加します。

_ChatScreenState の最後に次のメソッドを追加します。

@override
void dispose() {
  for (var message in _messages){
    message.animationController.dispose();
  }
  super.dispose();
}

b2f84ff91b0e1396.png これで正しいコードになりましたが、形は整っていません。コードペインで右クリックし、[Reformat Code with dartfmt] を選択します。

b2f84ff91b0e1396.png アプリをホットリロードします(実行中のアプリにチャット メッセージが含まれている場合はホット リスタートします)。メッセージを入力すると、アニメーション効果を確認できます。

さらにアニメーションを試してみる場合は、以下の例を参考にしてください。

  • _handleSubmitted() メソッドで指定する duration の値を変更すると、アニメーションを速くしたり遅くしたりできます。
  • Curves クラスで定義する定数を使用すると、アニメーション カーブを変更できます。
  • ContainerSizeTransition ではなく FadeTransition ウィジェットでラップすると、フェードイン アニメーション効果を作成できます。

トラブルシューティング

アプリが正常に動作しない場合は、入力ミスを探してください。必要に応じて、次のリンクのコードを確認してから、先に進んでください。

このステップ(省略可能)では、送信するテキストがある場合にのみ送信ボタンを有効にしたり、長いメッセージを折り返したり、Android や iOS でネイティブな外観になるようカスタマイズしたりするような、細かな部分を洗練させる作業を行います。

送信ボタンを状況対応にする

現時点では、入力フィールドにテキストがない場合でも送信ボタンが表示されています。送信するテキストがフィールドにあるかどうかによって、ボタンの外観を変更しましょう。

b2f84ff91b0e1396.png _isComposing を定義します。ユーザーが入力フィールドに入力すると true になる非公開変数です。

class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
  final List<ChatMessage> _messages = [];
  final _textController = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  bool _isComposing = false;            // NEW

b2f84ff91b0e1396.png onChanged() コールバック メソッドを _ChatScreenState. に追加します。

_buildTextComposer() メソッドで、TextField プロパティに onChanged プロパティを追加し、onSubmitted プロパティを更新します。

Flexible(
  child: TextField(
    controller: _textController,
    onChanged: (String text) {            // NEW
      setState(() {                       // NEW
        _isComposing = text.isNotEmpty;   // NEW
      });                                 // NEW
    },                                    // NEW
    onSubmitted: _isComposing ? _handleSubmitted : null, // MODIFIED
    decoration:
        InputDecoration.collapsed(hintText: 'Send a message'),
    focusNode: _focusNode,
  ),
),

b2f84ff91b0e1396.png _ChatScreenState.onPressed() コールバック メソッドを更新します。

引き続き _buildTextComposer() メソッドで、IconButtononPressed プロパティを更新します。

Container(
  margin: EdgeInsets.symmetric(horizontal: 4.0),
  child: IconButton(
      icon: const Icon(Icons.send),
      onPressed: _isComposing                            // MODIFIED
          ? () => _handleSubmitted(_textController.text) // MODIFIED
          : null,                                        // MODIFIED
      )
      ...
)

b2f84ff91b0e1396.png テキスト フィールドをクリアするときに _isComposing を false に設定するように _handleSubmitted を変更します。

void _handleSubmitted(String text) {
  _textController.clear();
  setState(() {                             // NEW
    _isComposing = false;                   // NEW
  });                                       // NEW

  ChatMessage message = ChatMessage(
  ...

cf1e10b838bf60ee.png 確認内容

  • onChanged コールバックで、ユーザーがテキストを編集したことを TextField に通知しています。このメソッドは、フィールドの現在の値から変化するたびに TextField から呼び出されます。
  • onChanged コールバックは、フィールドにテキストが含まれているときに _isComposing の値が true に変更されるように、setState() を呼び出します。
  • _isComposing が false の場合、onPressed プロパティは null に設定されます。
  • onSubmitted プロパティも、メッセージ リストに空の文字列を追加しないように修正されています。
  • _isComposing 変数で、送信ボタンの動作と外観を制御します。
  • テキスト フィールドに文字列を入力すると、_isComposingtrue, となり、ボタンの色は Theme.of(context).accentColor に設定されます。送信ボタンを押すと、フレームワークから _handleSubmitted() が呼び出されます。
  • テキスト フィールドに何も入力しなかった場合、_isComposingfalse, で、ウィジェットの onPressed プロパティは null に設定され、送信ボタンは無効になります。フレームワークが自動的にボタンの色を Theme.of(context).disabledColor に変更します。

b2f84ff91b0e1396.png アプリをホットリロードして、アプリを試してみましょう。

長い行を折り返す

メッセージを表示する UI の幅を超えるチャット メッセージを送信する場合、メッセージ全体が表示されるように各行を折り返す必要があります。現時点では、はみ出した行は切り捨てられ、オーバーフロー エラーが視覚的に表示されます。テキストを正しく折り返すには、Expanded ウィジェットにテキストを入れるのが簡単です。

b2f84ff91b0e1396.png Column ウィジェットを Expanded ウィジェットでラップします。

  1. ChatMessagebuild() メソッドで、ContainerRow 内の Column ウィジェットを選択します。
  2. Option+Return(macOS)または Alt+Enter(Linux と Windows)を押してメニューを表示します。
  3. Expanded,」と入力し、オブジェクトの候補から [Expanded] を選択します。

次のコードサンプルは、ChatMessage クラスの変更後を示しています。

...
Container(
  margin: const EdgeInsets.only(right: 16.0),
  child: CircleAvatar(child: Text(_name[0])),
),
Expanded(            // NEW
  child: Column(     // MODIFIED
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(_name, style: Theme.of(context).textTheme.headline4),
      Container(
        margin: EdgeInsets.only(top: 5.0),
        child: Text(text),
      ),
    ],
  ),
),                    // NEW
...

cf1e10b838bf60ee.png 確認内容

Expanded ウィジェットを使用すると、子ウィジェット(Column など)に、子ウィジェットに関するレイアウト制約(この場合は Column の幅)を適用できます。ここでは、通常はその内容によって決まる Text ウィジェットの幅を制約しています。

Android 向けと iOS 向けにカスタマイズする

FriendlyChatApp クラスの build() メソッドにテーマと簡単なロジックを追加すると、アプリの UI を自然な外観にできます。このステップでは、異なるプライマリ カラーとアクセント カラーのセットを適用するプラットフォーム テーマを定義します。Android ではマテリアル デザイン IconButton、iOS では CupertinoButton を使用するように、送信ボタンをカスタマイズします。

b2f84ff91b0e1396.png 次のコードを main.dartmain() メソッドの後に追加します。

final ThemeData kIOSTheme = ThemeData(
  primarySwatch: Colors.orange,
  primaryColor: Colors.grey[100],
  primaryColorBrightness: Brightness.light,
);

final ThemeData kDefaultTheme = ThemeData(
  primarySwatch: Colors.purple,
  accentColor: Colors.orangeAccent[400],
);

cf1e10b838bf60ee.png 確認内容

  • kDefaultTheme ThemeData オブジェクトで、Android 用の色(紫色にオレンジ色のアクセント)を指定します。
  • kIOSTheme ThemeData オブジェクトで、iOS 用の色(薄いグレーにオレンジ色のアクセント)を指定します。

b2f84ff91b0e1396.png 次の FriendlyChatApp クラスを変更して、アプリの MaterialApp ウィジェットの theme プロパティを使ってテーマを変更します。

  1. ファイルの先頭で foundation パッケージをインポートします。
import 'package:flutter/foundation.dart';  // NEW
import 'package:flutter/material.dart';
  1. FriendlyChatApp クラスを変更して、該当するテーマを選択します。
class FriendlyChatApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FriendlyChat',
      theme: defaultTargetPlatform == TargetPlatform.iOS // NEW
        ? kIOSTheme                                      // NEW
        : kDefaultTheme,                                 // NEW
      home: ChatScreen(),
    );
  }
}

b2f84ff91b0e1396.png AppBar ウィジェット(アプリの UI の上部にあるバナー)のテーマを変更します。

  1. _ChatScreenStatebuild() メソッドで、次のコードの行を見つけます。
      appBar: AppBar(title: Text('FriendlyChat')),
  1. 2 つの右かっこ「))」の間にカーソルを置き、カンマを入力し、Return キーを押して改行します。
  2. 次の 2 行を追加します。
elevation:
   Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0, // NEW
  1. コードペインで右クリックし、[Reformat Code with dartfmt] を選択します。

cf1e10b838bf60ee.png 確認内容

  • テーマを選択するために、最上位の defaultTargetPlatform プロパティと条件演算子を使用しています。
  • elevation プロパティで、AppBar の z 座標を定義しています。z 座標値 4.0 では定義済みの影が付き(Android)、値 0.0 では影なしになります(iOS)。

b2f84ff91b0e1396.png Android 向けと iOS 向けに送信アイコンをカスタマイズします。

  1. 次のインポートを main.dart の先頭に追加します。
import 'package:flutter/cupertino.dart';   // NEW
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
  1. _ChatScreenState_buildTextComposer() メソッドで、IconButtonContainer の子として割り当てる行を変更します。プラットフォームに関する条件付きになるように割り当てを変更します。iOS の場合は CupertinoButton を使用し、それ以外の場合は IconButton のままにします。
Container(
   margin: EdgeInsets.symmetric(horizontal: 4.0),
   child: Theme.of(context).platform == TargetPlatform.iOS ? // MODIFIED
   CupertinoButton(                                          // NEW
     child: Text('Send'),                                    // NEW
     onPressed: _isComposing                                 // NEW
         ? () =>  _handleSubmitted(_textController.text)     // NEW
         : null,) :                                          // NEW
   IconButton(                                               // MODIFIED
       icon: const Icon(Icons.send),
       onPressed: _isComposing ?
           () =>  _handleSubmitted(_textController.text) : null,
       )
   ),

b2f84ff91b0e1396.png Container ウィジェットで最上位の Column をラップし、その上端に薄いグレーの境界線を付けます。

この境界線により、iOS でアプリバーとアプリ本体を視覚的に区別できます。Android で境界線を非表示にするには、前のコードサンプルでアプリバーに使用されているのと同じロジックを適用します。

  1. _ChatScreenStatebuild() メソッドで、body: の後に表示される Column を選択します。
  2. Option+Return(macOS)または Alt+Enter(Linux と Windows)を押してメニューを表示し、[Wrap with Container] を選択します。
  3. この Column の後、Container の終わりよりも前に、プラットフォームに応じて適切なボタンを追加するコード(下記)を追加します。
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('FriendlyChat'),
      elevation:
          Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0, // NEW
    ),
    body: Container(
        child: Column(
          children: [
            Flexible(
              child: ListView.builder(
                padding: EdgeInsets.all(8.0),
                reverse: true,
                itemBuilder: (_, int index) => _messages[index],
                itemCount: _messages.length,
              ),
            ),
            Divider(height: 1.0),
            Container(
              decoration: BoxDecoration(color: Theme.of(context).cardColor),
              child: _buildTextComposer(),
            ),
          ],
        ),
        decoration: Theme.of(context).platform == TargetPlatform.iOS // NEW
            ? BoxDecoration(                                 // NEW
                border: Border(                              // NEW
                  top: BorderSide(color: Colors.grey[200]!), // NEW
                ),                                           // NEW
              )                                              // NEW
            : null),                                         // MODIFIED
  );
}

b2f84ff91b0e1396.png アプリをホットリロードします。Android と iOS で異なる色、影、アイコンボタンが表示されるはずです。

Pixel 3XL

iPhone 11

トラブルシューティング

アプリが正常に動作しない場合は、入力ミスを探してください。必要に応じて、次のリンクのコードを確認してから、先に進んでください。

これで完了です

Flutter フレームワークを使用してクロス プラットフォームのモバイルアプリを作成するための基本を学びました。

学習した内容

  • Flutter アプリをゼロから作成する方法
  • Android Studio と IntelliJ のショートカットの使用方法
  • エミュレータ、シミュレータ、デバイスで Flutter アプリの実行、ホットリロード、デバッグを行う方法
  • ウィジェットとアニメーションを使用してユーザー インターフェースをカスタマイズする方法
  • Android 用と iOS 用にユーザー インターフェースをカスタマイズする方法

次のステップ

他の Flutter Codelab を試す。

Flutter についてさらに学ぶ。

キーボード ショートカットについて詳しくは、以下をご覧ください。

参考としてサンプルを読むためにサンプルコードをダウンロードすることや、特定のセクションの Codelab を始めることをおすすめします。Codelab のサンプルコードを取得するには、ターミナルから次のコマンドを実行します。

 git clone https://github.com/flutter/codelabs

この Codelab のサンプルコードは friendly_chat フォルダにあります。番号付きステップの各フォルダは、この Codelab の番号付きステップの最終的なコードと同じものです。いずれかのステップの lib/main.dart ファイルのコードを DartPad のインスタンスにドロップして、そこから実行することもできます。