今日的程式碼 => GITHUB
BottomNavigationBar
的 index
改變後,再改回來時,頁面要記住最後離開時的畫面BottomNavigationBar
的 index
時,畫面都是同一個。BottomNavigationBar
顯示在下方,有時候不需要。這邊我來給大家看今天會用到的畫面,
如果看不太懂這個在寫什麼的話,可以參考 Day-3 Route設定 的教學文章
class RouteName {
/// 控制 BottomBar 的畫面
static const String bottomBar = 'bottomBar';
/// index=2 會用到的畫面,用來展示切換後畫面會被記住
static const String home_page_1 = 'home_page_1';
static const String home_page_2 = 'home_page_2';
/// index=2 會用到的畫面,用來展示,只要按到 bottomBar 的按鈕,把上就會變成沒有 bottomBar 的畫面
static const String second_page = 'second_page';
/// index=2 會用到的畫面,用來展示切換後畫面不會被記住
static const String third_page_1 = 'third_page_1';
static const String third_page_2 = 'third_page_2';
static const String third_page_3 = 'third_page_3';
/// index == 3 aaa 單存一個畫面,用來當作展示的跳板
/// 按到 bottomBar 的按鈕時,會先有 bottomBar,跳頁後變成,沒有 B bottomBar 的畫面
static const String aaa = 'aaa';
static const String bbb = 'bbb';
}
這是由上而下的樹狀圖
|App
|---|SecondPage|----------------------------------------------------
|---|BottomBarRoutePage| |
|---|------------------|NavigatorHomePage |
|---|------------------|-----------------|HomePage1 |
|---|------------------|-----------------|HomePage2 |
|---|------------------|SizedBox(BottomBarRoutePage 會直接把他跳到 SecondPage)
|---|------------------|NavigatorThirdPage
|---|------------------|------------------|ThirdPage1
|---|------------------|------------------|ThirdPage2
|---|------------------|------------------|ThirdPage3
|---|------------------|AAA
|---|------------------|---|BBB
這邊是用來控制 BottomBar 的,和上一篇 Flutter - BottomNavigationBar(上)Animation 有一點類似,
官方文件 Key
是 Widget
、Element
的識別符號。 只有當新的 Widget
的 Key
與當前 Element
中 Widget
的 Key
相同時,它才會被用來更新現有的 Element
。 Key
在具有相同父級的 Element
之間必須是唯一的。
widget
在 app
中的任何位置更改其 parent
而不丟失狀態。例:不同的螢幕,顯示相同的 widget
globalkey
唯一定義了某個 element
,它使你能夠訪問 element
相關的對象。例:不同的 widget
訪問狀態這邊之所以需要 globalkey
是因為我需要將它傳給下一個元件,使這個狀態會被記住,這樣下次回來的時候,就會是離開的時候的狀態。
WillPopScope
是 Android 的後退鍵,可以用來控制他,這邊 WillPopScope
的邏輯是,當我按下 WillPopScope
,會去用 mabyepop
的方式,如果可以 pop
的話就 pop
,不行的話就不做動作。
class BottomBarRoutePage extends StatefulWidget {
const BottomBarRoutePage({required this.pageIndex});
/// initialize page index
final int pageIndex;
@override
_BottomBarRoutePageState createState() => _BottomBarRoutePageState();
}
class _BottomBarRoutePageState extends State<BottomBarRoutePage> {
/// page index
late int _pageIndex;
/// GlobalKey of navigator
Map<int, GlobalKey> navigatorKeys = {
0: GlobalKey(),
1: GlobalKey(),
2: GlobalKey(),
3: GlobalKey(),
};
@override
void initState() {
_pageIndex = widget.pageIndex;
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: WillPopScope(
onWillPop: () async {
/// maybePop the current index context
return !await Navigator.maybePop(
navigatorKeys[_pageIndex]!.currentState!.context);
},
child: IndexedStack(
index: _pageIndex,
children: <Widget>[
NavigatorHomePage(navigatorKey: navigatorKeys[0]!),
SizedBox(), // 整個換頁。不要底部的 bottomBar
NavigatorThirdPage(
// 這邊是因為跳下一夜的時候不希望底下還有 bottomBar
navigatorKey: navigatorKeys[2]!),
AAA()
],
),
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
showSelectedLabels: false,
showUnselectedLabels: false,
selectedItemColor: Colors.red,
unselectedItemColor: Colors.black,
items: <BottomNavigationBarItem>[
/// when index = 0, after the page A is changed to page B, then changed the index to 1, and changed the index back to 0, the screen page will be B page,
/// And there will be bottomNavigationBar in the process
/// 在 index = 0 時,把A 畫面跳到 B 畫面後,把 index 改成 1,再把 index 改回 1,此時畫面一會在 B 畫面,並且在過程中都會有 bottomNavigationBar 存在
BottomNavigationBarItem(
icon: Icon(Icons.people_outline),
title: Text('c'),
),
/// When index = 1, after the page A is changed to screen B, there'll be no bottomNavigationBar under the B page,
/// and then back to the A page, there will be a bottomNavigationBar at the bottom of the A page
/// 在 index = 1 時,把整夜 bottomBar 切換成 A 畫面跳到 B 畫面後,B 畫面的下方不會有 bottomNavigationBar,此時再跳回道 A 畫面,A 畫面下方會有 BottomBar
BottomNavigationBarItem(
icon: Icon(Icons.add_to_photos_rounded),
title: Text('b'),
),
/// after the page changed, index changed, will not remember the page when you left, and the page will be the same every time when you re-enter index = 2.
/// 跳頁後,更換 index,並不會記住離開時的畫面,每次重新進入 index = 2 時,畫面都會一樣。
BottomNavigationBarItem(
icon: Icon(Icons.access_alarm),
title: Text('b'),
),
/// normal behavior
/// 一般行為
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text('c'),
),
],
currentIndex: _pageIndex,
onTap: (index) {
setState(() {
// index 0 1 2 3
/// when index == 1 then change to another Widget.
/// index == 1 時,代表他從 bottom_bar_route_page 跳到 新的畫面
if (index == 1) {
Navigator.pushNamedAndRemoveUntil(
context, RouteName.second_page, (route) => false);
} else if (index == 2) {
/// create a new GlobalKey, and the page will be the same every time when you re-enter
navigatorKeys[2] = GlobalKey();
_pageIndex = index;
} else {
_pageIndex = index;
}
});
},
),
);
}
}
這邊他會包成一個 Navigator。
什麼事 Navigator 呢?
官網介紹,我的理解,他也是一個路由器。
這邊我拿到 BottomBarRoutePage 的 Key,是為了讓畫面記住狀態。
onGenerateRoute
= 告知我的路由器是哪個
initialRoute
= 告知 App 開啟後第一個畫面是什麼
class NavigatorHomePage extends StatelessWidget {
NavigatorHomePage({required this.navigatorKey});
final GlobalKey navigatorKey;
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
onGenerateRoute: (RouteSettings settings) =>
MyRouter.generateRoute(settings),
initialRoute: RouteName.home_page_1,
//initialRoute: RouteName.third_page_1, <- NavigatorThirdPage 就要把上面註解,這邊不要註解
);
}
}
他的概念很像是我的 App 底下有一個『根(root)』路由器,然後 BottomNavigation
算是 App 底下的一個畫面,在這個 BottomNavigation
底下還有兩個路由器。
app
| - app(root) 的路由器
| bottomNavigation
| - - - - - - - -| NavigatorHomePage 的路由器
| - - - - - - - -| NavigatorThirdPage 的路由器
只要是在 bottomNavigation
的 index == NavigatorHomePage 的 index
畫面的話(也就是現在 bottomNavigation 是顯示 NavigatorHomePage 這一頁),之後再 pushNamed 的時候會有一些特性
BottomNavigation
的 index
並不會變動BottomNavigation
BottomNavigation 的 index
變動後,再回來時,會記住最後離開時的頁面是哪一個。如果想要跳頁後,不會顯示 BottomNavigationBar
的話,就
將 rootNavigator 設定為 true,代表他跳到 app 底下的 root route 的畫面。
Navigator.of(context,rootNavigator: true).pushNamed(RouteName.third_page_3),
如果想要按下 bottomBar 的按鈕時,就直接轉換成沒有 bottomBarItem 的畫面的話,可以直接在 BottomNavigationBar 的 onTap : (index){}
裡面做手腳,類似範例的這幾行。
onTap: (index) {
setState(() {
if (index == 1) {
Navigator.pushNamedAndRemoveUntil(
context, RouteName.second_page, (route) => false);
}
});
},