注)以下の記事は2020年10月31日時点のコードラボの内容に従って記述していますが、
↓raised_button.dartより抜粋
###このクラス(RaisedButtonクラス)は廃止されました。代わりに[ElevatedButton]を使用してください。
FlatButton、RaisedButton、およびOutlineButtonは、それぞれTextButton、ElevatedButton、およびOutlinedButtonに置き換えられました。
ButtonThemeは、TextButtonTheme、ElevatedButtonTheme、およびOutlinedButtonThemeに置き換えられました。
元のクラス(FlatButton、RaisedButton、およびOutlineButton)は間もなく非推奨になります。それらを使用するコードを移行してください。
新しいボタンとボタンテーマクラスの詳細な移行ガイドがあります
[flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide)。
「廃止されました」と書いてますが、2020年10月31日時点では動いています。
これからはTextButton、ElevatedButton、およびOutlinedButtonを
使いましょう。
Contents
1. Introduction
導入
Flutterのマテリアルデザインとマテリアルコンポーネントとは何ですか?
マテリアルデザインは、大胆で美しいデジタル製品を構築するためのシステムです。 一貫した一連の原則とコンポーネントの下でスタイル、ブランディング、インタラクション、モーションを統合することにより、製品チームは最大のデザインの可能性を実現できます。
Flutterのマテリアルコンポーネント(MDC-Flutter)は、アプリやプラットフォーム全体で一貫したユーザーエクスペリエンスを作成するコンポーネントのライブラリと、設計とエンジニアリングを統合します。
マテリアルデザインシステムが進化するにつれて、これらのコンポーネントは、Googleのフロントエンド開発標準に準拠して、一貫したピクセルパーフェクトな実装を保証するように更新されます。 MDCは、Android、iOS、およびWebでも使用できます。
このコードラボでは、MDCFlutterのいくつかのコンポーネントを使用してログインページを作成します。
作るもの
このコードラボは、衣料品や家庭用品を販売するeコマースアプリであるShrineというアプリの作成をガイドする4つのコードラボの最初のものです。 MDC-Flutterを使用して、ブランドやスタイルを反映するようにコンポーネントをカスタマイズする方法を示します。
このコードラボでは、以下を含むShrineのログインページを作成します。
- 神社のロゴの画像
- アプリの名前(神社)
- 2つのテキストフィールド。1つはユーザー名の入力用、もう1つはパスワード用です。
- 2つのボタン
android
このコードラボで登場するMDCコンポーネント
- Text field
- Button
- Ripple (it is a visual form of feedback for touch events)
2. Set up your Flutter environment
工事中🏗
3. Download the codelab starter app
工事中🏗
4. Add TextField widgets
まず、ログインページに2つのテキストフィールドを追加します。ここで、ユーザーはユーザー名とパスワードを入力します。 フローティングラベルを表示し、タッチリップルをアクティブにするTextFieldウィジェットを使用します。
マテリアルのテキストフィールドは、ブランドの柔軟性を向上させるために大幅に強化されており、使いやすさとビジュアルデザインを向上させるための広範なユーザーエクスペリエンス(UX)の調査に裏打ちされた改善が施されています。
新しいテキストフィールドの詳細については、マテリアルガイドラインの記事をご覧ください。
このページは、主にListViewで構成されており、子をスクロール可能な列に配置します。 下部にテキストフィールドを配置しましょう。
Add the TextField widgets
テキストフィールドウィジェットを追加する。
コード内のTODOには番号が含まれています。これらは、変更を実装するコードラボ番号を指します。
SizedBox(高さ:120.0)の後に2つの新しいテキストフィールドとスペーサーを追加します。
// TODO: Add TextField widgets (101) // [Name] TextField( decoration: InputDecoration( filled: true, labelText: 'Username', ), ), // spacer SizedBox(height: 12.0), // [Password] TextField( decoration: InputDecoration( filled: true, labelText: 'Password', ), obscureText: true, ),
テキストフィールドにはそれぞれ、InputDecorationウィジェットを受け取るdecoration:フィールドがあります。
filled:フィールドは、テキストフィールドの背景が薄く塗りつぶされていることを意味し、テキストフィールドのタップまたはタッチのターゲット領域を認識しやすくします。
2番目のテキストフィールドのobscureText:true値は、ユーザーが入力した入力を、パスワードに適した●に自動的に置き換えます。
passwordが見えないようにする。
ホットリロードを実行するプロジェクトを(ショートカット:command + s)で保存します。
保存すると自動的にホットリロードが行われる。
これで、ユーザー名とパスワードの2つのテキストフィールドがあるページが表示されます。 フローティングラベルのアニメーションをご覧ください。
「フローティングラベルのアニメーション」とは、テキストフィールドにフォーカスするとラベル(「Username」「Password」)が大きくなるアニメーションのこと、らしい。
アプリが更新されない場合は、もう一度[再生]ボタンをクリックするか、[停止]をクリックしてから[再生]をクリックしてください。
基本的にホットリロードではアプリの状態(state)はそのままなので、それで問題無いアプリなら問題無いのですが、状態が関係するようなアプリでは、ホットリロードでは辻褄が合わなくておかしな挙動になることがあります。
そういう場合はホットリスタート、あるいは「停止→実行」をする必要があります。
TextFieldの機能は次のとおりです。
TextFieldウィジェットの外観は簡単に変更できます。 装飾フィールドには、InputDecoration値を指定します。
MDCテキストフィールドには、デフォルトでタッチフィードバック(MDCリップルまたは「インク」と呼ばれます)が表示されます。
FormFieldは、フォームにフィールドを埋め込むための特別な機能を備えた同様のウィジェットです。
TextFieldクラスのドキュメントはこちらです。
次に、ログインページに「キャンセル」と「次へ」の2つのボタンを追加します。 FlatButton(マテリアルガイドラインでは「Textボタン」と呼ばれます)とRaisedButton(「Containedボタン」と呼ばれます)の2種類のMDCボタンウィジェットを使用します。
TextボタンとContainedボタンのどちらかを選択する
Containedボタンを単純に2つ表示するのではダメなんでしょうか? 各ボタンタイプは、他のアクションよりも重要なアクションを示します。レイアウトには、(他のボタンよりも)目立つボタンが1つ含まれている必要があります。 これにより、他のボタンの重要性が低くなることが明らかになります。 この目立つボタンは、アプリを進めるためにユーザーに最も実行してほしいアクションを表しています。
私たちがユーザーにしてほしい優先順位が低い行動は、ログインをキャンセルすることです。 Containedボタンは、その隆起した外観で目を引くので、より重要なアクションに使用する必要があります。 比較すると、左側のプレーンテキストボタンはあまり強調されていないように見えます。
onPressed:フィールドに空のブロックがあるのはなぜですか?
nullを渡した場合、またはフィールドを含めなかった場合(デフォルトではnull)、ボタンは無効になります。 タッチに関するフィードバックはなく、それらが有効になっている動作についての良いアイデアを得ることができませんでした。 空のブロックを使用すると、それらが無効になるのを防ぎます。ボタンとその相互作用の詳細については、アプリへの相互作用の追加を参照してください。
//login.dart import 'package:flutter/material.dart'; class LoginPage extends StatefulWidget { @override _LoginPageState createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { // TODO: Add text editing controllers (101) @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: ListView( padding: EdgeInsets.symmetric(horizontal: 24.0), children: <Widget>[ SizedBox(height: 80.0), Column( children: <Widget>[ Image.asset('assets/diamond.png'), SizedBox(height: 16.0), Text('SHRINE'), ], ), SizedBox(height: 120.0), // TODO: Wrap Username with AccentColorOverride (103) // TODO: Remove filled: true values (103) // TODO: Wrap Password with AccentColorOverride (103) TextField( decoration: InputDecoration( fillColor:Colors.amber, filled: true, labelText: 'Username', ), ), // spacer SizedBox(height: 12.0), // [Password] TextField( decoration: InputDecoration( fillColor:Colors.lightBlueAccent, filled: true, labelText: 'Password', ), obscureText: true, ), ButtonBar( //←追加 // TODO: Add a beveled rectangular border to CANCEL (103) children: <Widget>[ FlatButton( child: Text('CANCEL'), onPressed: () { // TODO: Clear the text fields (101) }, ), // TODO: Add an elevation to NEXT (103) // TODO: Add a beveled rectangular border to NEXT (103) RaisedButton( child: Text('NEXT'), onPressed: () { // TODO: Show the next page (101) }, ), ], ), ], ), ), ); } }
ButtonBarがレイアウト作業を処理します。 ボタンを水平に配置するため、現在のButtonThemeのパディングに従って、ボタンが隣り合って表示されます。 (これについては、codelab MDC-103で詳しく説明します。)
ボタンに触れると、他に何も起こらずにインクリップルアニメーションが開始されます。 onPressed:に設定してある空の関数に機能を追加して、Cancelボタンがテキストフィールドをクリアし、Nextボタンが画面を閉じるようにします。
(1)Add TextEditingControllers
TextEditingControllersウィジェットを使う。
テキストフィールドの値をクリアできるようにするために、TextEditingControllersウィジェットを追加してテキストを制御します。
_LoginPageStateクラスの宣言のすぐ下に、コントローラーを最終変数として追加します。(コードを参照してください。)
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
最初のテキストフィールドのcontroller:フィールドで、_usernameControllerを設定します。
TextField(
controller: _usernameController,
2番目のテキストフィールドのcontroller:フィールドで、_passwordControllerを設定します。
TextField(
controller: _passwordController,
(2)Edit onPressed
FlatButtonのonPressed:パラメータに、各コントローラーをクリアするコマンド(関数・コールバック)を追加します。
// TODO: Clear the text fields (101)
_usernameController.clear();
_passwordController.clear();
プロジェクトを保存します。 これで、テキストフィールドに何かを入力した後、Cancelボタンを押すと、各フィールドがクリアされます。
このログインフォームは良好です。 ユーザーをShrineアプリの残りの部分に進めましょう。
ここまでのlogin.dartファイル↓
//login.dart import 'package:flutter/material.dart'; class LoginPage extends StatefulWidget { @override _LoginPageState createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { //↓(1)Add TextEditingControllersで追加 final _usernameController = TextEditingController(); //←追加 final _passwordController = TextEditingController(); //←追加 @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: ListView( padding: EdgeInsets.symmetric(horizontal: 24.0), children: <Widget>[ SizedBox(height: 80.0), Column( children: <Widget>[ Image.asset('assets/diamond.png'), SizedBox(height: 16.0), Text('SHRINE'), ], ), SizedBox(height: 120.0), // TODO: Wrap Username with AccentColorOverride (103) // TODO: Remove filled: true values (103) // TODO: Wrap Password with AccentColorOverride (103) TextField( //↓(1)Add TextEditingControllersで追加 controller:_usernameController, //←追加 decoration: InputDecoration( fillColor:Colors.amber, filled: true, labelText: 'Username', ), ), // spacer SizedBox(height: 12.0), // [Password] TextField( //↓(1)Add TextEditingControllersで追加 controller:_passwordController, //←追加 decoration: InputDecoration( fillColor:Colors.lightBlueAccent, filled: true, labelText: 'Password', ), obscureText: true, ), ButtonBar( // TODO: Add a beveled rectangular border to CANCEL (103) children: <Widget>[ FlatButton( child: Text('CANCEL'), onPressed: () { //↓(2)Edit onPressedで追加。 _usernameController.clear(); _passwordController.clear(); }, ), // TODO: Add an elevation to NEXT (103) // TODO: Add a beveled rectangular border to NEXT (103) RaisedButton( child: Text('NEXT'), onPressed: () { // TODO: Show the next page (101) }, ), ], ), ], ), ), ); } }
(3)Pop
このビュー(ページ)を閉じるには、このページ(Flutterではルート(route)と呼ぶ)をナビゲーションスタックからポップ(削除、pop)します。
ナビゲーターは、iOSのUINavigationControllerと同じようにルートのスタックを維持します。 ルートを押すと、そのルートがスタックの一番上に配置されます。 スタックをポップすると、最後に追加されたルートが削除されます。 アプリのapp.dartで、initialRoute: ‘/ login’を呼び出すと、ホームで渡されるものに加えて、ログイン画面がナビゲーターに追加されます。
ルートとナビゲーションの詳細については、ナビゲーションの基本をご覧ください。
RaisedButtonのonPressed:関数で、ナビゲーターから最新のルートをポップします。
Navigator.pop(context);
最後に、home.dartを開き、ScaffoldでresizeToAvoidBottomInsetをfalseに設定します。
return Scaffold(
// TODO: Add app bar (102)
// TODO: Add a grid view (102)
body: Center(
child: Text('You did it!'),
),
// TODO: Set resizeToAvoidBottomInset (101)
resizeToAvoidBottomInset: false,
);
これを行うことで、キーボードの外観がホームページやそのウィジェットのサイズを変更しないことが保証されます。
それでおしまい! プロジェクトを保存します。 実行して、[Next]をクリックします。
Nextボタンを押すとLoginPageウィジェットがpopされて、「You did it!」と表示されるHomePageウィジェットが表示されました。
6. All done
終わりです。
テキストフィールドとボタンを追加し、レイアウトコードを考慮する必要はほとんどありませんでした。 Flutterのマテリアルコンポーネントには多くのスタイルがあり、ほとんど簡単に画面に配置できます。
Next steps
テキストフィールドとボタンはマテリアルシステムの2つのコアコンポーネントですが、他にもたくさんあります。 Flutterのマテリアルコンポーネントライブラリで残りのウィジェットを調べることもできます。
または、MDC-102:Material Design Structure and Layoutにアクセスして、Flutter用のMDC-102でカバーされているコンポーネントについて学習してください。
参考
https://codelabs.developers.google.com/codelabs/mdc-101-flutter
https://codelabs.developers.google.com/codelabs/mdc-101-flutter#0
https://codelabs.developers.google.com/codelabs/mdc-101-flutter#1
https://codelabs.developers.google.com/codelabs/mdc-101-flutter#2
https://codelabs.developers.google.com/codelabs/mdc-101-flutter#3
https://codelabs.developers.google.com/codelabs/mdc-101-flutter#4