【问题标题】:Need a persistent/same Bottom Navigation Bar for all screens - Flutter所有屏幕都需要一个持久/相同的底部导航栏 - Flutter
【发布时间】:2019-02-19 13:20:06
【问题描述】:

我是颤振和飞镖的初学者。我一直在尝试在我的应用程序的三个不同页面上实现navigationBar。切换适用于单个页面,但我在所有页面上保持活动和非活动选项卡状态时遇到问题。似乎当它导航到另一个页面时,我也失去了标签的活动状态。这是我的代码。

AppFooter.dart

import 'package:flutter/material.dart';

class AppFooter extends StatefulWidget {
  @override
  _AppFooterState createState() => _AppFooterState();
}

class _AppFooterState extends State<AppFooter> {
  int index = 0;
  @override
  Widget build(BuildContext context) {
    return new Theme(
      data: Theme.of(context).copyWith(
          // sets the background color of the `BottomNavigationBar`
          canvasColor: Colors.white,
          // sets the active color of the `BottomNavigationBar` if `Brightness` is light
          primaryColor: Colors.green,
          textTheme: Theme.of(context)
              .textTheme
              .copyWith(caption: new TextStyle(color: Colors.grey))),
      child: new BottomNavigationBar(
          type: BottomNavigationBarType.fixed,
          currentIndex: index,
          onTap: (int index) {
            setState(() {
              this.index = index;
            });
          switch (index){
            case 0:  Navigator.of(context).pushNamed('/dashboard');
            break;
            case 1:  Navigator.of(context).pushNamed('/medical centre');
            break;
            case 2:  Navigator.of(context).pushNamed('/history');
            break;

          }

          },
          items: [
            new BottomNavigationBarItem(
                backgroundColor: Colors.white,
                icon: index==0?new Image.asset('assets/images/dashboard_active.png'):new Image.asset('assets/images/dashboard_inactive.png'),
                title: new Text('Dashboard', style: new TextStyle(fontSize: 12.0))),
           new BottomNavigationBarItem(
               backgroundColor: Colors.white,
               icon: index==1?new Image.asset('assets/images/medical_sevice_active.png'):new Image.asset('assets/images/medical_sevice_inactive.png'),
               title: new Text('Health Services', style: new TextStyle(fontSize: 12.0))),
            new BottomNavigationBarItem(
                icon: InkWell(
                  child: Icon(
                    Icons.format_align_left,
                   // color: green,
                    size: 20.0,
                  ),
                ),
                title: new Text('History', style: new TextStyle(fontSize: 12.0))),
          ]),
    );
  }
}

【问题讨论】:

    标签: dart flutter


    【解决方案1】:

    如果我正确理解您的问题,您需要将底部导航栏保留在所有三个页面上。关于如何实现它,有一篇写得很好的文章。您可以在此处找到详细信息。

    https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf

    https://github.com/bizz84/nested-navigation-demo-flutter

    所有学分归原作者所有。

    【讨论】:

    • 从上面的例子中,有红色、绿色和蓝色导航。如果红屏包含很少的导航怎么办?
    • 我尝试了上面的方法,发现最大的问题是当我们更改1个选项卡时,它会触发其他选项卡的构建方法,我认为这对性能非常不利。只需在其他选项卡的构建方法中添加日志行,您会看到每次更改选项卡时都会调用日志行。
    • @TuanvanDuong 我认为构建每个其他选项卡的原因是为了保持状态;显然还有其他方法可以解决这个问题,但似乎这就是原因。
    • @Cl0ud-l3ss 我从here 找到了一个非常好的解决方案。这个解决方案甚至不使用CupertinoTabBar
    【解决方案2】:

    使用 PageViewbottomNavigationBar

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    /// This Widget is the main application widget.
    class MyApp extends StatelessWidget {
      static const String _title = 'Flutter App';
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: _title,
          home: App(),
        );
      }
    }
    
    class App extends StatefulWidget {
      App({Key key}) : super(key: key);
      _AppState createState() => _AppState();
    }
    
    class _AppState extends State<App> {
      PageController _myPage;
      var selectedPage;
    
      @override
      void initState() {
        super.initState();
        _myPage = PageController(initialPage: 1);
        selectedPage = 1;
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            body: PageView(
              physics: NeverScrollableScrollPhysics(),
              controller: _myPage,
              children: <Widget>[
                Center(
                  child: Text("Another Page"),
                ),
                Center(
                    child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text("Page 1"),
                    RaisedButton(
                      onPressed: () {
                        _myPage.jumpToPage(0);
                        setState(() {
                          selectedPage = 0;
                        });
                      },
                      child: Text("Go to another page"),
                    )
                  ],
                )),
                Center(child: Text("Page 2")),
                Center(child: Text("Page 3")),
              ],
            ),
            bottomNavigationBar: BottomAppBar(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: <Widget>[
                  IconButton(
                    icon: Icon(Icons.home),
                    color: selectedPage == 1 ? Colors.blue : Colors.grey,
                    onPressed: () {
                      _myPage.jumpToPage(1);
                      setState(() {
                        selectedPage = 1;
                      });
                    },
                  ),
                  IconButton(
                    icon: Icon(Icons.star),
                    color: selectedPage == 2 ? Colors.blue : Colors.grey,
                    onPressed: () {
                      _myPage.jumpToPage(2);
                      setState(() {
                        selectedPage = 2;
                      });
                    },
                  ),
                  IconButton(
                    icon: Icon(
                      Icons.settings,
                    ),
                    color: selectedPage == 3 ? Colors.blue : Colors.grey,
                    onPressed: () {
                      _myPage.jumpToPage(3);
                      setState(() {
                        selectedPage = 3;
                      });
                    },
                  ),
                ],
              ),
            ));
      }
    }
    

    此外,如果您想保留页面之间的状态,以便转到另一个页面不会导致前一页面丢失其状态,请使用AutomaticKeepAliveClientMixin

    另外,为了延迟加载页面,PageView.builder 是另一种解决方案。

    希望对你有帮助。

    【讨论】:

    • 请详细说明它将如何帮助将来可能面临此问题的人。
    • 是的,你是对的!抱歉,编辑了我的答案
    • 您正在同时加载所有页面,而且是不必要的。没有表现力。
    • @GuilhermeFerreira 看看这里:stackoverflow.com/questions/66404759/…
    • 如果我答对了问题,一旦在页面之间移动,他就会失去状态,理想情况下,我希望在页面之间保留状态,这就是为什么使用 PageView 是一个简单的解决方案。只需AutomaticKeepAliveClientMixin 在部分或所有页面中处理此问题很容易。 api.flutter.dev/flutter/widgets/… 另外,如果你想延迟加载页面,使用PageView.builder 也可以实现。
    【解决方案3】:

    另一个很好的解决方案是Bilal Shahid提供的persistent_bottom_nav_bar包。

    它易于使用,并为您提供了一堆features

    • 高度可定制的持久底部导航栏。
    • 能够推送带有或不带有底部导航栏的新屏幕。
    • 底部导航栏的20种样式。
    • 包括用于在有或没有底部导航栏的情况下推送屏幕的函数,即 pushNewScreen() 和 pushNewScreenWithRouteSettings()。
    • 基于 Flutter 的 Cupertino(iOS) 底部导航栏。
    • 对于特定选项卡可以是半透明的。
    • 导航栏的自定义样式。点击这里查看更多信息。 处理硬件/软件 Android 后退按钮。

    在我找到这个包之前,我遵循了@Abin 在他的回答中提到的文章中的解决方案。但是我遇到了问题,所有来自navbarscreens 都在navbar 的第一次加载时加载,这不是那么好。我没有解决这个问题,但幸运的是Bilal Shahid 提供了一个很好的解决方案。

    所有功劳归于他。

    【讨论】:

    • 这个库让事情变得简单。谢谢你。
    • 这应该是正确答案,效果很好
    【解决方案4】:

    只需复制并过去 :)

    main.dart:

    void main() async{
      runApp(MyGrillApp());
    }
    
    class MyGrillApp extends StatelessWidget {
      const MyGrillApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
    
          routes: {
            '/mainlayout': (context) => MainLayout(),
            '/page1': (context) => Page1(),
            '/page2': (context) => Page2(),
            '/page3': (context) => Page3(),
            '/page4': (context) => Page4(),
          },
          initialRoute: '/mainlayout',
        );
      }
    }
    

    main_layout.dart:

    class MainLayout extends StatefulWidget {
      @override
      _MainLayoutState createState() => _MainLayoutState();
    }
    
    class _MainLayoutState extends State<MainLayout> {
      int _currentIndex = 0;
    
      final _page1 = GlobalKey<NavigatorState>();
      final _page2 = GlobalKey<NavigatorState>();
      final _page3 = GlobalKey<NavigatorState>();
      final _page4 = GlobalKey<NavigatorState>();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          floatingActionButtonLocation: FloatingActionButtonLocation.miniCenterDocked,
          floatingActionButton: Padding(
            padding: const EdgeInsets.all(6.0),
            child: FloatingActionButton(
              backgroundColor: Colors.redAccent,
              child: const Icon(Icons.add, color: Colors.white),
              onPressed: () {
                // ToDo...
              },
            ),
          ),
          body: IndexedStack(
            index: _currentIndex,
            children: <Widget>[
              Navigator(
                key: _page1,
                onGenerateRoute: (route) => MaterialPageRoute(
                  settings: route,
                  builder: (context) => Page1(),
                ),
              ),
              Navigator(
                key: _page2,
                onGenerateRoute: (route) => MaterialPageRoute(
                  settings: route,
                  builder: (context) => Page2(),
                ),
              ),
              Navigator(
                key: _page3,
                onGenerateRoute: (route) => MaterialPageRoute(
                  settings: route,
                  builder: (context) => Page3(),
                ),
              ),
              Navigator(
                key: _page4,
                onGenerateRoute: (route) => MaterialPageRoute(
                  settings: route,
                  builder: (context) => Page4(),
                ),
              ),
            ],
          ),
          bottomNavigationBar: BottomAppBar(
            shape: const CircularNotchedRectangle(),
            clipBehavior: Clip.antiAlias,
            child: BottomNavigationBar(
              backgroundColor: Colors.white,
              currentIndex: _currentIndex,
              onTap: (index) {
                setState(() {
                  _currentIndex = index;
                });
              },
              type: BottomNavigationBarType.fixed,
              selectedItemColor: Colors.redAccent,
              unselectedItemColor: Colors.grey,
              showSelectedLabels: false,
              showUnselectedLabels: false,
              items: const <BottomNavigationBarItem>[
                BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
                BottomNavigationBarItem(icon: Icon(Icons.date_range), label: 'Statistics'),
                BottomNavigationBarItem(icon: Icon(Icons.wallet_giftcard), label: 'Wallet'),
                BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
              ],
            ),
          ),
        );
      }
    }
    

    详细信息屏幕:

    class ItemDetailsPage extends StatefulWidget {
      const ItemDetailsPage({Key? key}) : super(key: key);
    
      @override
      _ItemDetailsPageState createState() => _ItemDetailsPageState();
    }
    
    class _ItemDetailsPageState extends State<ItemDetailsPage> with AutomaticKeepAliveClientMixin{
      @override
      Widget build(BuildContext context) {
        super.build(context);
        return Scaffold(
            appBar: AppBar(
              backgroundColor: themeColorPrimary,
              title: Text('Item details',),
            ),
            body : Container(child: Text('Hello from details'),));
      }
    
      @override
      bool get wantKeepAlive => true;
    }
    

    关于我的解决方案中路由的说明

    如果您在路由时遇到麻烦

    Navigator.pushNamed(context, '/page3'); 
    

    或通过:

    Navigator.of(context).pushNamed(Page3());
    

    您可以使用 MaterialPageRoute 修复它:

    Navigator.pushReplacement(
      context,
      MaterialPageRoute(
        builder: (context) => Page3(),
      ),
    );
    

    【讨论】:

    • 如果,我在主页中,然后我使用底部导航器导航到页面 B。从页面 B 我的 AppBar 上有一个使用 Navigator.of(context).pushNamed 的操作按钮(Page_C) 转到页面 C。那么最后一个导航将不起作用。
    • @A.Ktns 感谢您的来信。你是对的,Navigator.of(context).pushNamed(Page_C);和 Navigator.pushNamed(context, '/path');将不再工作,而是使用 pushReplacement: Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => const NoGrillsPage(), ), );我会更新我的回答
    • 您的代码运行良好。但是当我从主页导航到其他页面(例如)并从底部导航栏点击主页时,应用程序不会返回主页。
    【解决方案5】:

    当您触摸/更改页面时,您可以使用 IndexedStack 来保持状态

    Scaffold(
      body: SafeArea(
        top: false,
        child: IndexedStack(
          //Permet de garder le state des vues même quand on change de vue
          index: _currentIndex,
          children: _children,
        ),
      ),
      bottomNavigationBar: BottomNavigationBar( items: [ ] ),
    );
    

    【讨论】:

      【解决方案6】:

      我创建了一个小而超级易于使用的包,可以让您实现这种效果CustomNavigator。 并在 Medium 上写了一篇关于它的教程,你可以找到它here

      原来是这样的

      // Here's the custom scaffold widget
      // It takes a normal scaffold with mandatory bottom navigation bar
      // and children who are your pages
      CustomScaffold(
        scaffold: Scaffold(
          bottomNavigationBar: BottomNavigationBar(
            items: _items,
          ),
        ),
      
        // Children are the pages that will be shown by every click
        // They should placed in order such as
        // `page 0` will be presented when `item 0` in the [BottomNavigationBar] clicked.
        children: <Widget>[
          Page('0'),
          Page('1'),
          Page('2'),
        ],
      
        // Called when one of the [items] is tapped.
        onItemTap: (index) {},
      );
      

      这个库很酷的地方在于它可以高效地工作。它创建了一个嵌套的导航器(这样做非常不愉快),并将它用于在您的小部件树中进行导航。 当然,您始终可以使用 MaterialApp 中的默认导航器

      【讨论】:

      • 您可以编辑您的答案并添加您认为可以回答问题的代码的相关部分吗?
      【解决方案7】:

      如果您正在寻找即使使用 IndexedStack 也能表现良好(不会不必要地构建选项卡/页面)的解决方案,请查看我的回答 here

      【讨论】:

      • 这非常适合我的用例,您可以观看解释如何使用它的 youtube 视频Here
      【解决方案8】:

      我强烈推荐使用堆栈。这让您几乎可以完全控制想要显示底部应用栏的方式和时间。

      使用botttomAppBar 列出您要显示的所有页面。假设有三个图标。

      final List<Widget> pages=[FirstScreen(),SecondScreen(),ThirdScreen()];
      

      在构建方法中

      Scaffold(
       child: Stack(
        children: <Widget>[
          Navigator(
                    key: _navigatorKey,
                    onGenerateRoute: (RouteSettings settings) { 
                      return MaterialPageRoute(
                        builder: (BuildContext context) => pages[cur_ind],
                 );
              },
            ),
         ],
         bottomNavigationBar: BottomNavigationBar(
      
              onTap: (int index){
                 
                  setState(() {
                   
                    cur_ind=index;
                    
                  });
      
              },
              currentIndex: cur_ind,
              fixedColor: Colors.green, //let's say
      
              items: [
                BottomNavigationBarItem(
                  icon: Icon(Icons.home),
                  title: Text('Home'),
                ),
                BottomNavigationBarItem(
                  icon: Icon(Icons.mail),
                  title: Text('Messages'),
                ),
                BottomNavigationBarItem(
                    icon: Icon(Icons.person), title: Text('Profile'))
              ],
            ),
        ),
      ),
      

      其中 cur_ind 是用于控制显示哪个页面的变量。而且由于 body 是堆叠的,Bottom Navigation Bar 将始终保持不变。

      【讨论】:

        【解决方案9】:

        我正在开发 express_app plugin 的测试版,它可以达到所需的结果。

        两天前,我实现了一个附加功能,您可以在其中设置ExpressHome,它可以是您树的任何部分,当然除了设置您的路线。更改路线时,ExpressHome 下的所有内容都将仅更改,其余内容保持不变(即您可以轻松拥有永久栏。

        今晚我将发布一个更新的版本,如果您想要关于您的用例的特定演示,请告诉我。

        【讨论】:

          【解决方案10】:

          我也有这个问题...经过几天的研究,我遇到了这个包 persistent_bottom_nav_bar: ^4.0.0

          很容易实现。

          【讨论】:

            【解决方案11】:

            您可以使用脚手架小部件来包含整个屏幕,然后将 IndexedStack 小部件作为 Body 选项,然后在您最喜欢的底部导航栏实现的脚手架小部件中使用底部导航选项

            Scaffold(
                  // here is the IndexedStack as body
                  body: IndexedStack(
                      index: this._bottomNavIndex,
                      children: [MangaGridView(), FavoriteManga()]),
                  backgroundColor: Colors.black,
                  bottomNavigationBar: AnimatedBottomNavigationBar(
                    icons: [
                      Icons.home_outlined,
                      Icons.favorite_border,
                      Icons.settings,
                    ],
                    inactiveColor: Colors.black,
                    activeIndex: this._bottomNavIndex,
                    gapLocation: GapLocation.none,
                    activeColor: Theme.of(context).primaryColor,
                    notchSmoothness: NotchSmoothness.verySmoothEdge,
                    leftCornerRadius: 32,
                    rightCornerRadius: 32,
                    onTap: (index) => setState(() => this._bottomNavIndex = index),
                    height: 70,
                    splashColor: Theme.of(context).primaryColor,
                    splashRadius: 40.0,
                    splashSpeedInMilliseconds: 400,
                    iconSize: 34,
                  ),
                );
            

            【讨论】:

              【解决方案12】:

              对于将来寻找此功能的任何人auto_route 使用 AutoTabsScaffold 只需很少的样板即可很好地处理此问题。

              小部件构建(上下文){ 返回 AutoTabsScaffold( 路线:常量 [ 图书路由器(), 帐户路由器(), ], bottomNavigationBuilder: (_, tabsRouter) { 返回底部导航栏( 当前索引:tabsRouter.activeIndex, onTap: tabsRouter.setActiveIndex, 项目: [ 底部导航栏项( 图标:图标(图标.book), 标签:“书籍”, ), 底部导航栏项( 图标:图标(Icons.account_box), 标签:'帐户', ), ], ); }, ); }

              【讨论】:

                【解决方案13】:

                Navigator.of(context).pushNamed(); 用于带有页面转换的导航。所以,在这种情况下,方法不匹配。

                您可以将BottomNavigationBarScaffold 一起使用。

                示例代码:

                
                class AppFooter extends StatefulWidget {
                  @override
                  _AppFooterState createState() => _AppFooterState();
                }
                
                class _AppFooterState extends State<AppFooter> {
                  int _currentIndex = 0;
                
                  List<Widget> _pages = [
                    Text("page1"),
                    Text("page2"),
                    Text("page3"),
                  ];
                
                
                  @override
                  Widget build(BuildContext context) {
                    return Scaffold(
                      body: _pages[_currentIndex],
                      bottomNavigationBar: BottomNavigationBar(
                        type: BottomNavigationBarType.fixed,
                        currentIndex: _currentIndex,
                        onTap: (int index) {
                          setState(() {
                            _currentIndex = index;
                          });
                        },
                        items: [
                          new BottomNavigationBarItem(
                              backgroundColor: Colors.white,
                              icon: _currentIndex == 0
                                  ? new Image.asset('assets/images/dashboard_active.png')
                                  : new Image.asset('assets/images/dashboard_inactive.png'),
                              title:
                                  new Text('Dashboard', style: new TextStyle(fontSize: 12.0))),
                          new BottomNavigationBarItem(
                              backgroundColor: Colors.white,
                              icon: _currentIndex == 1
                                  ? new Image.asset('assets/images/medical_sevice_active.png')
                                  : new Image.asset(
                                      'assets/images/medical_sevice_inactive.png'),
                              title: new Text('Health Services',
                                  style: new TextStyle(fontSize: 12.0))),
                          new BottomNavigationBarItem(
                              icon: InkWell(
                                child: Icon(
                                  Icons.format_align_left,
                                  // color: green,
                                  size: 20.0,
                                ),
                              ),
                              title: new Text('History', style: new TextStyle(fontSize: 12.0))),
                        ],
                      ),
                    );
                  }
                }
                
                

                【讨论】:

                • 有没有办法让底部导航栏在推到新屏幕时保持静止?
                • 对不起,我误解了您的问题。我会修正这个答案。请稍等。
                • 你好兄弟。你有什么办法吗?
                • 很抱歉回复晚了。我确定了我的答案。我希望这会对你有所帮助。
                • 这不起作用。请参阅下面@Abin 的回答,了解如何实现所需的操作
                【解决方案14】:

                只需将索引变量设为静态即可 喜欢:

                static int index = 0;
                

                【讨论】:

                  猜你喜欢
                  • 2020-03-06
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-09-12
                  • 2023-02-26
                  • 2021-07-12
                  • 2020-10-13
                  相关资源
                  最近更新 更多