現状の問題
BottomNavigationBar
で表示したWidgetから何も考えずにNavigator.of(context).push()
で遷移すると以下のように、画面遷移と同時にBottomNavBarが消えてしまう。
表示した画面から遷移するコードは以下のようなイメージ。
_buildCategoryButton() {
return
child: RaisedButton(
child: Row(
children: [
Text(
'カテゴリ名',
style: TextStyle(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.w500,
),
),
],
),
onPressed: () => onPressedCategory(1),
),
);
}
void onPressedCategory(int id) {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => CategoryScreen()));
}
}
画面遷移に伴ってBottomNavigationBar
は消えてほしくないので、頑張ってキープしてみる。
各Widget/methodについてちょろっと解説
-
Navigator.of(context)
The state from the closest instance of this class that encloses the given context.
- Flutterのプロジェクトにおいて大体ルートウィジェットとして登録されている
MaterialApp
が持つNavigator
を探しに行くメソッド。
-
MaterialPageRoute
はMaterialDesignっぽいアニメーションで遷移するためのウィジェット。
なぜBottomNavBarは消えるのか
Navigator.of
はNavigator
を探しに行くので、BottomNavBarよりも上のNavigator
を見つけてそれ以下のWidgetをRebuildするため。
WidgetTreeは以下の様になっている。
▼ MyApp
▼ MaterialApp
▼ <some other widgets>
▼ Navigator
▼ <some other widgets>
▼ App
▼ Scaffold
▼ body: <some other widgets>
▼ BottomNavigationBar
つまり、BottomNavigationBar
の祖先ではないNavigator
を使うことができれば、BottomNavigationBar
は消えずに画面遷移が出来る。
CupertinoTab... を使ってpersistな下タブを手に入れろ
あまり紹介されていないのだけれど、Cupertino
にはCupertinoTab...
という固定された下タブを前提として画面を構成するためのクラス群が提供されている。
↓のcodelabでcupertinoを前提としてアプリを作る記事があり、ここで紹介されている。
https://codelabs.developers.google.com/codelabs/flutter-cupertino/#3
Cupertino tab has a separate scaffold because on iOS, the bottom tab is commonly persistent above nested routes rather than inside pages.
(iOSデザインに寄せているから下タブがpersistだよ、みたいなことを言っているがMaterial DesignのガイドラインでもハッキリWhen used, the bottom navigation bar appears at the bottom of every screen.
と言われている。なんなんだ一体。)
話が逸れたけれども、以下のように実装すると下タブが固定される。
...
class _MainPageState extends State<MainPage> {
...
@override
Widget build(BuildContext) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: const <BottomNavigationBarItem> [
BottomNavigationBarItem(
icon: ...
title ...
),
BottomNavigationBarItem(
icon: ...
title ...
),
],
onTap: _onItemTapped(index), // 実は無くても動く
currentIndex: _selectedIndex, // 実は無くても動く
),
tabBuilder: (context, index) {
switch (index) {
case 0: // 1番左のタブが選ばれた時の画面
return CupertinoTabView(builder: (context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
leading: Icon(...), // ページのヘッダ左のアイコン
),
child: SearchScreen(), // 表示したい画面のWidget
);
});
case 1: // ほぼ同じなので割愛
...
}
}
),
},
}
Cupertino周りのクラス1行解説
-
CupertinoTabScaffold
- 固定された下タブを持つアプリの構造をscaffoldしてくれるwidget。内部的には↓の
CupertinoTabBar
がタップされたかを勝手にリッスンしてくれるので、自前でonTap
にコールバックを書かなくても動くのはこいつのおかげ。tabBar:
で表示するタブの内容を、tabBuilder:
でTabItemがタップされた時に表示する画面を管理する。
- 固定された下タブを持つアプリの構造をscaffoldしてくれるwidget。内部的には↓の
-
CupertinoTabBar
-
BottomNavigationBarItem
を子に持って、下タブを表示する。
-
-
CupertinoTabView
- タブに紐づく1つの画面と、Navigatorを持つWidget。CupertinoTabViewがNavigatorを持っているというのはミソで、こいつのおかげで各タブごとに画面遷移を戻ったり進んだり出来る。
-
CupertinoPageScaffold
- iOSっぽいアプリの構造をscaffoldしてくれるwidget。ぶっちゃけ
CupertinoTabScaffold
の下タブがなくてNavBarの設定ができるやつぐらいにしか知りません。
- iOSっぽいアプリの構造をscaffoldしてくれるwidget。ぶっちゃけ
-
CupertinoNavigationBar
- ↑のCupertinoPageScaffoldの
navigationBar:
に入るwidget。leadingにアイコンを設定すると勝手に戻ったりしてくれる偉い子。
- ↑のCupertinoPageScaffoldの
CupertinoPageScaffold.child で呼ばれるWidgetもCupertinoPageScaffoldしなきゃいけなかったりするの?
いいえ。タブ内に表示される画面のルートWidgetはシンプルなScaffold
で問題ありません。以下のような感じです。
class SearchScreen extends StatefulWidget {
SearchScreen({Key key}) : super(key: key);
@override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ギフトを探す'),
),
body: Container(
...
)
)
}
}
何のことはない、普通のStatefullWidgetで大丈夫です。
最終的に出来上がるもの
ドン。完璧ですね。
以下のgifを見れば分かる通り、一度画面遷移をした後に、別のタブに切り替えから再度最初のタブに切り替えても画面遷移がそのまま保持されていますね。
無事に下タブを保持したまま画面遷移できました。
お疲れ様でした。
その他
- …書き終わってから気づいたのですが、標準のBottomNavigationBarだと各タブをタップした際に、タップされたアイコンが大きくなったり微妙にアニメーションしたりするマイクロインタラクションが入ってるのですが、Cupertinoだとシンプルに切り替わってるだけになってます。どうしても標準のBottomNavigationBarを使いたい場合、CupertinoScaffoldが実装してることを自前で書く方法があるのでこちらで対処しましょう。
本文中に載らなかった参考URL
- おじさんがCupertinoTabScaffoldでコーディングしてる動画