【第五天 - Fluter BottomNavigationBar(下)行为分析】

前言

今日的程序码 => GITHUB


我们在开发的时候常常会遇到几种情况

  • 需要 BottomNavigationBarindex 改变後,再改回来时,页面要记住最後离开时的画面
  • 每次进入相同的 BottomNavigationBarindex 时,画面都是同一个。
  • 有时候页面需要 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

页面 - BottomBarRoutePage

这边是用来控制 BottomBar 的,和上一篇 Flutter - BottomNavigationBar(上)Animation 有一点类似,

用到的第一个技术:GlobalKey

官方文件 KeyWidgetElement 的识别符号。 只有当新的 WidgetKey 与当前 ElementWidgetKey 相同时,它才会被用来更新现有的 ElementKey 在具有相同父级的 Element 之间必须是唯一的。

  • 允许 widgetapp 中的任何位置更改其 parent 而不丢失状态。例:不同的萤幕,显示相同的 widget
  • globalkey 唯一定义了某个 element,它使你能够访问 element 相关的对象。例:不同的 widget 访问状态

这边之所以需要 globalkey 是因为我需要将它传给下一个元件,使这个状态会被记住,这样下次回来的时候,就会是离开的时候的状态。

用到的第二个技术 WillPopScope

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;
           }
         });
       },
     ),
   );
 }
}

页面 - NavigationHome + NavigatorThirdPage

这边他会包成一个 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 的路由器

只要是在 bottomNavigationindex == NavigatorHomePage 的 index 画面的话(也就是现在 bottomNavigation 是显示 NavigatorHomePage 这一页),之後再 pushNamed 的时候会有一些特性

  • BottomNavigationindex 并不会变动
  • 底下都会有 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);
            }
          });
        },

<<:  [Day-19] R语言 - 分群应用(一) k - prototype类别补值 - 下 ( Fill.NA with k - prototype in R.Studio )

>>:  Day-04 Python 的 Gradient 计算

Not only MordernWeb,But also Data Club—阅读建议、文章索引、结语

绘制自己铁人赛文章资料所构成的图表 最後一天就来做个本次文章系列的总结吧,我用puppteer爬取了...

Day03 - Amazon ECS Anywhere 基础说明与建置(上)

这系列主要就是讲Amazon ECS Anywhere 所以先来看看阳春版怎麽建立出来 基础运行元件...

Day7: IAM users、group建立

上一篇定义了IAM里面会看到的名词,今天我们来看一下AWS Console里面要怎麽建置IAM Us...

Day 13 ( 中级 ) 大型数字 ( 图形数字 )

大型数字 ( 图形数字 ) 教学原文参考:大型数字 ( 图形数字 ) 如果要在 Scratch 3 ...

Day12 - 搜寻文章作者及合并方法

今天做点简单的事情,先把搜寻作者的部份给加进来。 Layout也跟昨天一样先多加一行: 基本上前置作...