BotttomNavigationBarを設定したアプリにおい下の BottomBNavBarを残したまま画面のボタンで遷移するやり方について理解したく備忘録として残します。(2023.03現在)
Flutterバージョン
Flutter 3.0.5
Tools • Dart 2.17.6 • DevTools 2.12.2
完成図
必要なライブラリ
今回はHooksを使用して状態管理を行います。
現在のバージョンを公式からコピしてpubspec.yamlに記載。
TabBarに必要なものを宣言
NavigaitoBarとは別のファイルに必要なものを宣言(同じファイル内でも問題ないですが、可読性のためファイルを分けました)
列挙型で中身は二つのタブの名前、アイコン、本体のインスタンスです。
// 列挙型で各タブのタイトル、アイコン、ページのインスタンスを宣言
enum TabItem {
home(
title: 'ホーム',
icon: Icons.home,
page: HomePage(),
),
favorite(
title: 'お気に入り',
icon: Icons.favorite,
page: SecondPage(),
);
// 上のhome(),favorite()の引数を宣言し引数として使えるように設定
// 参考サイト: https://www.fuwamaki.com/article/380
const TabItem({
required this.title,
required this.icon,
required this.page,
});
final String title;
final IconData icon;
final Widget page;
}
ホームページ
メインのホームページはボタンを配置しただけの簡単なものです。
繊維の仕方もオーソドックスなNavigator.of()の遷移です。
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('home'),
),
body: Center(
child: ElevatedButton(
child: Text('テストページへ'),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return TestPage();
}));
},
),
),
);
}
}
遷移先のページ
遷移先の注意点としてScaffoldウィジェットでappbarを宣言しないと戻るボタンが表示されません。
class TestPage extends StatelessWidget {
const TestPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar( // アプリバーを設定しないと戻るボタンが表示されない
title: Text('test')
),
body: Center(
child: Text('テストページ')
),
);
}
}
BottomNavigationBarを管理するメインページ
一番重要な部分です。大事な部分を解説していこうと思います。
ページ全体
// Map型でTabITemに対して一つのキーをもつデータコレクションを用意
final _navigatorKeys = <TabItem, GlobalKey<NavigatorState>>{
TabItem.home: GlobalKey<NavigatorState>(), //ホームページ用のグローバルキー
TabItem.favorite: GlobalKey<NavigatorState>(),//お気に入りページ用のグローバルキー
};
// HookWidgetで状態管理
class MainApp extends HookWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
// useState で選択状態の管理 初期値をhomeに設定
// 参考サイト: https://qiita.com/mkosuke/items/f88419d0f4d41ed6d858
final currentTab = useState(TabItem.home);
return MaterialApp(
home: Scaffold(
body: Stack( // スタックWidgetを使って状態を保つ
// Offstageは表示/非表示を設定したいWidgetをchildに入れる
// enumのvaluesは全ての配列ケースを取得できるもの
children: TabItem.values.map((tabItem) => Offstage(
offstage: currentTab.value != tabItem,
//各ページの Navigator に NavigatorState を持った Key を渡す
child: Navigator(
key: _navigatorKeys[tabItem],
onGenerateRoute: (settings) {
return MaterialPageRoute<Widget>(
builder: (context) => tabItem.page,
);
},
),
),
).toList(),
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: TabItem.values.indexOf(currentTab.value),
items: TabItem.values.map((tabItem) => BottomNavigationBarItem(
icon: Icon(tabItem.icon),
label: tabItem.title,
),
).toList(),
onTap: (index) {
// ③ 選択済なら第一階層まで pop / 未選択なら currentTab に指定
final selectedTab = TabItem.values[index];
if (currentTab.value == selectedTab) {
_navigatorKeys[selectedTab]
?.currentState
?.popUntil((route) => route.isFirst);
} else {
currentTab.value = selectedTab;
}
},
),
),
);
}
}
グローバルキーを用意
各ページのグローバルキーを宣言します。
// Map型でTabITemに対して一つのキーをもつデータコレクションを用意
final _navigatorKeys = <TabItem, GlobalKey<NavigatorState>>{
TabItem.home: GlobalKey<NavigatorState>(), //ホームページ用のグローバルキー
TabItem.favorite: GlobalKey<NavigatorState>(),//お気に入りページ用のグローバルキー
};
useStateで状態管理
flutter_hooksのuseStateを使用して最初のページにホームページを設定できるように宣言します。
final currentTab = useState(TabItem.home);
bodyの中身
このアプリのbodyであるStackWidgteの中身です。
私が重要かなと思う以下の3つのポイントについて解説していきます。
①map.toListで表示
②Offstage
③Navigatorで管理
children: TabItem.values.map((tabItem) => Offstage(
offstage: currentTab.value != tabItem,
//各ページの Navigator に NavigatorState を持った Key を渡す
child: Navigator(
key: _navigatorKeys[tabItem],
onGenerateRoute: (settings) {
return MaterialPageRoute<Widget>(
builder: (context) => tabItem.page,
);
},
),
),
).toList(),
①map.toListで表示
map.toList()で表示することによって自分が表示したいアイテムをループ文を使用せずにシンプルに実装できます。
②Offstage
Offstageウィジェットはウィジェットの表示、非表示を管理できるウィジェットです。
表示、非表示を管理してcurrentTabのvalueが引数のtabItemでなければ非表示にしています。
③Navigatorで管理
Navigatorは状態管理に使用するWidgteです。
各ページのNavigatorに最初に宣言したkeyを渡しています。
key: _navigatorKeys[tabItem],
そしてルートに各タブを渡して遷移できるようにしてきます。
onGenerateRoute: (settings) {
return MaterialPageRoute<Widget>(
builder: (context) => tabItem.page,
);
},
BottomNavigationBar
あとは、BottomNavigationBarを設定していきます。
ここで重要なポイントは下記の3つです。
①現在のページ設定
②BottomNavigationBarのitem設定
③タップした際の処理
BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: TabItem.values.indexOf(currentTab.value),
items: TabItem.values.map((tabItem) => BottomNavigationBarItem(
icon: Icon(tabItem.icon),
label: tabItem.title,
),
).toList(),
onTap: (index) {
// 選択済なら第一階層まで pop / 未選択なら currentTab に指定
final selectedTab = TabItem.values[index];
if (currentTab.value == selectedTab) {
_navigatorKeys[selectedTab]
?.currentState
?.popUntil((route) => route.isFirst);
} else {
currentTab.value = selectedTab;
}
},
),
①現在のページ設定
現在のページ(今いるページ)には、currentTabのvalueつまりuseStateで取得した情報をindexOfで設定します。
currentIndex: TabItem.values.indexOf(currentTab.value),
②BottomNavigationBarのitem設定
BottomNavigationBarのアイテムもMap.tolistで設定しています。
引数のtabItemからそれぞれもアイコンとタイトルを設定しています。
③タップした際の処理
まず、引数index番目のTabItem(列挙型でまとめたページの中身)を取得できるように宣言。
final selectedTab = TabItem.values[index];
BottomNavigationBarをタップした際の処理です。
useStateで取得したもの(currentTab.value)と今、選択されてるタブが同じならルートの最初つまり選択したページにpop(戻る処理)をする。
そうでなければuseStateで取得したもの(currentTab.value)に今、選択されているタブを代入するというイメージです。
onTap: (index) {
if (currentTab.value == selectedTab) {
_navigatorKeys[selectedTab]
?.currentState
?.popUntil((route) => route.isFirst);
} else {
currentTab.value = selectedTab;
}
},
参考サイト
下記サイトを参考に不明点を自分なりにまとめてみました。非常に参考になるのでこちらもチェックしてみてください。
最後に
Hooksを使っての遷移について記載しましたが、曖昧な部分もありますので気になる点があればぜひフィードバックをいただけると幸いです。
ではまた。
コメント