前回、FlutterでTwitterのクライアントアプリを作ろうということでヘッダーを追加したところまでやりました。
-
FlutterでTwitterクライアント作成①ヘッダー追加
前回、Flutterの環境作成と開発者ツールの導入をしました。 さて、本格的に作るとして何しよう? …特に浮かばないので、おなじみTwitterのクライアントを作ってみようと思います。 今回はプロジェ ...
続きを見る
今回はフッターを追加して画面を切り替えられるようにしたいと思います。
目次
フッターを追加する
ヘッダーと同じように、フッターも別ファイルに書きます。
footer.dartを作成して以下を追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import 'package:flutter/material.dart'; class Footer extends StatefulWidget { @override _Footer createState() => _Footer(); } class _Footer extends State { @override Widget build(BuildContext context) { return BottomNavigationBar( items: const [ BottomNavigationBarItem( icon: Icon(Icons.home), title: Text('ホーム'), ), BottomNavigationBarItem( icon: Icon(Icons.search), title: Text('検索'), ), BottomNavigationBarItem( icon: Icon(Icons.notifications), title: Text('通知'), ), BottomNavigationBarItem( icon: Icon(Icons.mail), title: Text('メッセージ'), ), ], selectedItemColor: Colors.pinkAccent, unselectedItemColor: Colors.black45, ); } } |
ヘッダーと同じようにStatefulWidgetを使ってます。
フッターの場合はBottomNavigationBarの中にBottomNavigationBarItemを追加して返します。
Twitterアプリを見るとアイコンの下にテキストがなかったので、同じように省略したらエラーになってしまいました。。。
BottomNavigationBarItemを使う場合はtitle必須のようです。
最後の2行は色の指定です。
1 2 |
selectedItemColor: Colors.pinkAccent, // 選択中アイテムの色 unselectedItemColor: Colors.black45, // 非選択のアイテムの色 |
main.dartを修正
作成したフッターを読み込みます。
ヘッダーと同じように修正します。
※コード一部省略
1 2 3 4 5 6 7 8 9 10 11 |
import 'package:flutter/material.dart'; import 'header.dart'; import 'footer.dart'; // フッター追加 ・・・ home: Scaffold( appBar: Header(), body: Center(child: Text('ホーム')), bottomNavigationBar: Footer(), // Footerを追加 ), ・・・ |
保存するとこんな感じ。
現段階だと、タップはできるけど何も変化しない状態です。
フッターのボタンをタップで切り替える
footer.dartを修正します。
こちらを参考にしました。
全体はこんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
class _Footer extends State<Footer> { int _selectedIndex = 0; final _bottomNavigationBarItems = <BottomNavigationBarItem>[]; // アイコン情報 final Map _footerIcons = { 'ホーム' : Icons.home, '検索' : Icons.search, '通知' : Icons.notifications, 'メッセージ' : Icons.mail, }; // アイコン順番 final List _footerItemOrder = [ 'ホーム', '検索', '通知', 'メッセージ', ]; @override void initState() { super.initState(); for ( var i = 0; i < _footerItemOrder.length; i++) { _bottomNavigationBarItems.add(_CreateIcon(_footerItemOrder[i])); } } // アイコンを作成する BottomNavigationBarItem _CreateIcon(String key) { return BottomNavigationBarItem( icon: Icon(_footerIcons[key]), title: Text(key), ); } void _onItemTapped(int index) { setState( () { _selectedIndex = index; }); } @override Widget build(BuildContext context) { return BottomNavigationBar( type: BottomNavigationBarType.fixed, items: _bottomNavigationBarItems, currentIndex: _selectedIndex, onTap: _onItemTapped, selectedItemColor: Colors.pinkAccent, unselectedItemColor: Colors.black45, ); } } |
アイコン情報の定義
アイコンのtitleとIconをそれぞれMapで紐づけました(_footerIcons)
本当はこれだけで済ませたかったんですが。。。
今選択しているIconのインデックスが必要だったので、別途アイコンの順番を定義しました(_footerItemOrder)
並び順を変える場合はこっちの順番だけ変えればいいようになってます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// アイコン情報 final Map _footerIcons = { 'ホーム' : Icons.home, '検索' : Icons.search, '通知' : Icons.notifications, 'メッセージ' : Icons.mail, }; // アイコン順番 final List _footerItemOrder = [ 'ホーム', '検索', '通知', 'メッセージ', ]; |
アイコンを作成する処理
この2つのメソッドでアイコンを作成しています。
initState()はクラスのコンストラクターが呼び出された直後に1回だけ呼ばれます。
ここで_footerItemOrderに定義された順番通りにアイコン名を渡して_CreateIconを実行。
_CreateIconは受け取ったアイコン名(ホームとか)を使ってBottomNavigationBarItemを作成。
_bottomNavigationBarItemsに必要なアイコンをすべて追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@override void initState() { super.initState(); for ( var i = 0; i < _footerItemOrder.length; i++) { _bottomNavigationBarItems.add(_CreateIcon(_footerItemOrder[i])); } } // アイコンを作成する BottomNavigationBarItem _CreateIcon(String key) { return BottomNavigationBarItem( icon: Icon(_footerIcons[key]), title: Text(key), ); } |
アイコンのタップイベント
BottomNavigationBarはタップされたときに該当アイコンのindexを返します。
ここではそれを受け取って、_selectedIndexに代入。
値を保持しているだけです。
1 2 3 4 5 |
void _onItemTapped(int index) { setState( () { _selectedIndex = index; }); } |
フッターの作成
BottomNavigationBarを作成します。
selectedItemColorとunselectedItemColorをそれぞれ指定することで、タップしたときのアイコンの色を自動で切り替えてくれます。
1 2 3 4 5 6 7 8 9 10 11 |
@override Widget build(BuildContext context) { return BottomNavigationBar( type: BottomNavigationBarType.fixed, // 選択時の見た目を指定 items: _bottomNavigationBarItems, // 作成したアイコン currentIndex: _selectedIndex, // 選択中のアイコンのインデックス onTap: _onItemTapped, // タップされたときのイベント selectedItemColor: Colors.pinkAccent, // 選択時の色 unselectedItemColor: Colors.black45, // 非選択時の色 ); } |
注意点
さて、ここまでを保存するのですが。
今回は1回しか呼ばれないinitState()の処理があるので、アプリの再起動が必要です。
Hot Restartを実行すると…。
アイコンタップでフッターの状態が切り替わるようになりました。
フッタータップで画面を切り替える
フッターアイコンと連動して画面を切り替えられるようにします。
画面を作成する
画面遷移したことがわかるくらいの簡単な画面を4画面用意します。
routesフォルダを作成し、以下4ファイルを追加しました。
※routeとはFlutterで画面を指します
- routes/home_route.dart
- routes/search_route.dart
- routes/notice_route.dart
- routes/message_route.dart
中身はimportを追加し、クラス名と文字の部分を画面に合わせて変えただけ。
ちなみに通知用画面はnotificationと訳されることが多いと思いますが、Flutterの既存クラスと重複してエラーが出るのでnoticeとします。
1 2 3 4 5 6 7 8 9 10 11 12 |
import 'package:flutter/material.dart'; import '../header.dart'; class Home extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: Header(), body: Center(child: Text('ホーム')), ); } } |
このファイルで各画面に遷移するので、ファイル名をfooter.dartからroot.dartに変更します。
ついでにクラス名もFooter→RootWidgetに変更します。
1つずつ変更するとミスが起きやすいので、全置換しましょう。
Ctrl + Hで置換用のウィンドウが表示されます。
ファイル名を変更したので、main.dartでエラーが出るようになりました。
こちらもファイル名を修正します。
1 2 3 4 5 6 |
// import 'footer.dart'; import 'root.dart'; ・・・ // bottomNavigationBar: Footer(), bottomNavigationBar: RootWidget(), ・・・ |
アイコンと画面を紐づける
アイコンがタップされたときに画面を切り替えられるようにします。
以下変更したところだけ。
作成したroutesを読み込みます。
1 2 3 4 |
import 'routes/home_route.dart'; import 'routes/search_route.dart'; import 'routes/notice_route.dart'; import 'routes/message_route.dart'; |
_RootWidgetIconsにrouteの情報を追加。
分割して情報を管理するのが嫌だったので、Mapで連想配列を入れ子にしました。
1 2 3 4 5 6 7 |
// アイコン情報 final Map _RootWidgetIcons = { 'ホーム' : {'icon': Icons.home, 'route': Home()}, '検索' : {'icon': Icons.search, 'route': Search()}, '通知' : {'icon': Icons.notifications, 'route': Notice()}, 'メッセージ' : {'icon': Icons.mail, 'route': Message()}, }; |
_RootWidgetIconsの情報の取り出し方を修正。
1 2 3 4 5 6 7 |
// アイコンを作成する BottomNavigationBarItem _CreateIcon(String key) { return BottomNavigationBarItem( icon: Icon(_RootWidgetIcons[key]['icon']), // iconを呼べるようにする title: Text(key), ); } |
returnをScaffoldにして、bodyでHome()などを呼べるようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@override Widget build(BuildContext context) { return Scaffold( body : _RootWidgetIcons[_footerItemOrder.asMap()[_selectedIndex]]['route'], bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, items: _bottomNavigationBarItems, currentIndex: _selectedIndex, onTap: _onItemTapped, selectedItemColor: Colors.pinkAccent, unselectedItemColor: Colors.black45, ), ); } |
保存すると、フッタータップで画面が切り替わるようになりました。
ヘッダーの文字を画面に合わせて変える
せっかく画面が切り替わるようになったのに、ヘッダーのタイトルがホームのままになっています。
画面に合わせてタイトルを変更するようにします。
まずはheader.dartを修正します。
1 2 3 4 5 6 7 |
class Header extends StatelessWidget with PreferredSizeWidget{ // ヘッダータイトルを格納する変数を追加 final String headerTitle; Header({this.headerTitle}); @override Size get preferredSize => Size.fromHeight(kToolbarHeight); |
titleでheaderTitleを使うようにします。
1 2 3 4 5 6 7 8 9 10 |
@override Widget build(BuildContext context) { return AppBar( leading: Padding( padding: const EdgeInsets.all(8.0), child: UserIcon(), ), title: Center( child: Text(headerTitle), ), |
次に各routeファイルを修正します。
home_route.dartを修正。
他のファイルも同じように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 |
class Home extends StatelessWidget { // 2回使うので変数に格納 final String screenName = 'ホーム'; @override Widget build(BuildContext context) { return Scaffold( appBar: Header(headerTitle: screenName), // header.dartで追加したheaderTitleにscreenNameを指定 body: Center(child: Text(screenName)), // screenNameに変更 ); } } |
これで保存してみると…。
画面と一緒にヘッダータイトルも切り替わるようになりました。
フッターもとりあえず完成
まだ中身は何もないですが、なんとなくアプリっぽくなってきました。
それにしてもFlutterはUI周りの処理が簡単でいいですね。
今回はここまで。
次回はホーム画面の中身を作っていこうと思います。
-
FlutterでTwitterクライアント作成③タイムライン取得
前回の続き。 今回はホーム画面にTwitterのタイムライン情報を読み込んでみます。 目次1 まずはお試し1.1 http通信を使えるようにする1.2 http通信して情報を表示する1.2.1 パッケ ...
続きを見る
ここまでのコードはGitHubにあるのでどうぞ。
とにかくリファレンスを読め
Flutterはまだ日本語の情報が少なくて、基本は英語のリファレンスに頼ることになります。
※昔よりはだいぶ増えたらしいですが
もちろん日本人が書いたソースも参考にはなりますが、力業で実装してたりもするので要注意。
こんなめんどくさいことしないといけないの?って思ったらリファレンスを読んだほうがいいです。
実はライブラリ側で便利に使えるpropertyが用意されてたりします。
例えば今回参考にしたサイトではアイコンのアクティブ状態を切り替えるのにメソッド2つ作ってましたけど
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
・・・ /// インデックスのアイテムをアクティベートする BottomNavigationBarItem _UpdateActiveState(int index) { return BottomNavigationBarItem( icon: Icon( _footerIcons[index], color: Colors.black87, ), title: Text( _footerItemNames[index], style: TextStyle( color: Colors.black87, ), ) ); } ・・・ |
リファレンス読んだらbuildに2行追加するだけで実現できました。
1 2 |
selectedItemColor: Colors.pinkAccent, unselectedItemColor: Colors.black45, |
Flutterはまだまだ発展途上なので、今不便なことも将来的には簡単になるかも?
しばらくはFlutterのバージョンが上がるたびにリファレンス読んだ方がいいかもしれませんね。