[Flutter入門(3)] Write your first Flutter app, part 2をやってみる
Flutterの公式サイトに、チュートリアルよりも前にやってみる用のコンテンツとして Write your first Flutter app というものが用意されています。
前回の記事 ではこれのPart 1を解説したので、今回は続きである Part 2 の解説をしていきたいと思います。
Step 1:イントロダクション
Part 2では以下の内容を学ぶらしいです。
- iOS、Android、Webでよくあるような見た目のアプリをFlutterで作る方法
- より素早い開発サイクルのためにホットリロードを使う方法
- Statefulウィジェットをインタラクティブにする方法
- 別の画面へのナビゲートを作成する方法
- アプリの見た目を「テーマ」を使って変更する方法
と原文に書いてあるんですが、ホットリロードについてはこのパートで特別詳しく書かれている様子はなかったです😅
出来上がるアプリはこのようなものになります。
Step 2:Flutter自体のセットアップ
Write your first Flutter app, part 1 の内容を参考にFlutter自体のセットアップを済ませておきます。
Step 3:アプリのベースを準備する
Write your first Flutter app, part 1 を終えている状態を前提として、続きで開発していくので、先にPart 1を終えておきましょう。
いつもどおり、iOSシミュレータを立ち上げて、アプリを起動したら、準備完了です。
$ open -a Simulator
$ cd startup_namer
$ flutter run
Step 4:リストにアイコンを追加する
まず、リストの各行にハートのアイコンを追加します。(次のステップで、アイコンをタップするとお気に入りとして保存されるような実装をしていきます)
_RandomWordsState
クラスに _saved
という Set
型のクラス変数を追加します。この Set
にユーザーがお気に入りに追加した単語ペアを保存する想定です。
Set
型は List
型と違って同じものを重複して登録できないので、ここでのニーズにはより適しています。
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
+ final _saved = Set<WordPair>();
final _biggerFont = TextStyle(fontSize: 18.0);
続いて、 _buildRow()
メソッドにおいて各行が保存済みかどうかを判別できるよう、 alreadySaved
という変数を用意しておきます。
また、各行にハートアイコンを追加しておきます。次のステップで alreadySaved
が機能するようになると、お気に入りに追加されているかどうかによってアイコンの見た目が変化します。
Widget _buildRow(WordPair pair) {
+ final alreadySaved = _saved.contains(pair);
+
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
+ trailing: Icon(
+ alreadySaved ? Icons.favorite : Icons.favorite_border,
+ color: alreadySaved ? Colors.red : null,
+ ),
);
}
この時点で見た目は以下のようになっています。
Step 5:インタラクティブにする
リストの各行をタップしたときに _saved
変数の中身を更新してお気に入りの追加・削除をできるようにします。
_buildRow()
メソッドで ListTile
を作る際に、 onTap
引数にクロージャーを渡すことでタップ時に何らかの処理を実行させることができます。
ここではウィジェットのステートを変更したいので、ただ _saved
変数を更新するだけでなく、 setState()
を呼び出すことでフレームワークに状態の変更を通知する必要があります。
setState()
を呼び出すと、ウィジェットのbuild()
メソッドが内部的に呼び出され、結果としてウィジェットのUIが再描画されます。
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair);
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
),
+ onTap: () {
+ setState(() {
+ if (alreadySaved) {
+ _saved.remove(pair);
+ } else {
+ _saved.add(pair);
+ }
+ });
+ },
);
}
これで、行をタップすることでアイコンの色がトグルするようになりました。
Step 6:画面遷移を実装する
今度は画面遷移を実装してみましょう。
Flutterでは、 Navigator
というクラスが画面遷移のスタックを管理してくれます。 Navigator
のスタックにルートをpushすることでそのルートへの画面遷移が発生し、逆にスタックからルートをpopすることで1つ前の画面に戻ることができます。
まずは、 _RandomWordsState
クラスの build()
メソッドを修正して、 AppBar
にリストアイコンを追加しましょう。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Startup Name Generator'),
+ actions: [
+ IconButton(icon: Icon(Icons.list), onPressed: _pushSaved),
+ ],
),
body: _buildSuggestions(),
);
}
リストアイコンをタップすると _pushSaved()
というクラスメソッドが実行されるように指定しています。ではこの _pushSaved()
メソッドを実装していきましょう。
_RandomWordsState
クラスに以下のように _pushSaved()
メソッドを追加します。
void _pushSaved() {
}
とりあえずこれで AppBar
の右端にリストアイコンが表示されますが、この時点ではタップしても特に何も起こりません。これから _pushSaved()
メソッドの中身を実装して、タップしたときに次の画面へ遷移して、お気に入り一覧画面を表示するようにしていきます。
まずは以下の内容まで書きましょう。
void _pushSaved() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
// 遷移先の画面を表すウィジェットを作ってreturnする
},
),
);
}
-
Navigator
のpush()
メソッドを実行して、新しいルートをスタックに追加する - 追加するルートは
MaterialPageRoute
クラスのインスタンスで、そのコンストラクタ引数builder
にクロージャーを渡して、そこで次の画面のウィジェットを作る
という流れになります。
では、次の画面を表すウィジェットを実際に構築するコードを書き足してみましょう。
void _pushSaved() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
+ final tiles = _saved.map(
+ (WordPair pair) {
+ return ListTile(
+ title: Text(
+ pair.asPascalCase,
+ style: _biggerFont,
+ ),
+ );
+ },
+ ).toList();
+
+ return Scaffold(
+ appBar: AppBar(
+ title: Text('Saved Suggestions'),
+ ),
+ body: ListView(children: tiles),
+ );
},
),
);
}
-
_saved
リストをmap
で回して、保存済みの単語ペアを元にListTile
のリストを作る - 上記で作った
ListTile
のリストをbody
とするようなScaffold
ウィジェットを作ってreturnする
ということをしています。
これで、リストアイコンをタップすると画面遷移が発生して以下のようなお気に入りリストが表示されるようになります。
この時点で機能としては十分なのですが、さらにもう一手間加えてこのリストに仕切り線を入れるようにします。
Part 1では「リストの偶数番目の要素なら仕切り線を入れる」という処理を自力で書きましたが、ここではもっと簡単な方法を用います。
void _pushSaved() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
final tiles = _saved.map(
(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
);
},
- ).toList();
+ );
+ final divided = ListTile.divideTiles(
+ context: context,
+ tiles: tiles,
+ ).toList();
return Scaffold(
appBar: AppBar(
title: Text('Saved Suggestions'),
),
- body: ListView(children: tiles),
+ body: ListView(children: divided),
);
},
),
);
}
ListTile.divideTiles()
という静的メソッドを利用して、 ListTile
のリストを元に 仕切り線を入れた状態の ListTile
のリスト を作成しました。
これで、下図のように仕切り線が入った状態でリストが表示されて見やすくなりました👍
Step 7:テーマを使ってUIを変更してみる
最後に、FlutterのMaterialデザイン実装における テーマ
という機能を使って、UIの見た目を切り替えるということをやってみましょう。
Flutterでは、何も指定しなければデフォルトのテーマが適用されますが、ThemeData クラス を設定することで自由にカラースキームを変更することができます。
例えば以下のように MyApp
の build()
メソッドを修正してみてください。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
+ theme: ThemeData(
+ primaryColor: Colors.white,
+ ),
home: RandomWords(),
);
}
}
AppBarなどの色が白に変わったはずです。
primaryColor
に Colors.white
を指定したことによって、Flutterで用意されている各種ウィジェットにおいて「ここは primaryColor
の色を付ける」と定義されているところがまとめて白に変わったわけです。
このように、ThemeData クラス で定義されている様々な変数を上書きしてあげることで、UIの見た目を統一感を保ったままカスタマイズすることができます。
Discussion