使用 Flutter 导航和路由时如何保持 BottomNavigationBar

Posted

技术标签:

【中文标题】使用 Flutter 导航和路由时如何保持 BottomNavigationBar【英文标题】:How to persist BottomNavigationBar when using Flutter navigation and routes 【发布时间】:2019-09-17 05:07:34 【问题描述】:

我正在制作一个包含 4 个不同页面的项目。我使用“BottomNavigationBar”小部件导航到每个页面。当按下“BottomNavigationBar”中的图标时,我会在 Scaffold 正文中显示不同的页面。我不使用任何路由,所以当用户按下 android 时,应用程序会关闭。我不想发生的事情。

我找到的所有指南在导航时都会重新加载整个“Scaffold”,但我只想更新“Scaffold”小部件的“body”属性。当Navigation.pop() 出现时,我再次只希望“脚手架”主体发生变化。

我在同一问题上找到了a post,但答案对我不起作用。

我可以尝试的另一种解决方法是制作自定义历史列表,然后在页面更改时更新该列表。按下后退按钮时捕获 OnWillPop 事件以更新页面。我没有尝试过,因为我觉得必须有更好的方法。

显示页面的 Scaffold 小部件。

  Widget createScaffold() 
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: EmptyAppBar(),
      body: _displayedPage,
      bottomNavigationBar: createBottomNavigationbar(),
    );
  

BottomNavigationBar 小部件。

  Widget createBottomNavigationbar() 
    return BottomNavigationBar(
      type: BottomNavigationBarType.fixed,
      currentIndex: _selectedIndex,
      onTap: _onItemTapped,
      items: [
        BottomNavigationBarItem(
          icon: new Icon(Icons.home,
              color: _selectedIndex == 0 ? selectedColor : unselectedColor),
          title: new Text('Home',
              style: new TextStyle(
                  color:
                  _selectedIndex == 0 ? selectedColor : unselectedColor)),
        ),
        BottomNavigationBarItem(
          icon: new Icon(Icons.show_chart,
              color: _selectedIndex == 1 ? selectedColor : unselectedColor),
          title: new Text('Month',
              style: new TextStyle(
                  color:
                  _selectedIndex == 1 ? selectedColor : unselectedColor)),
        ),
        BottomNavigationBarItem(
            icon: Icon(Icons.history,
                color: _selectedIndex == 2 ? selectedColor : unselectedColor),
            title: Text('History',
                style: new TextStyle(
                    color: _selectedIndex == 2
                        ? selectedColor
                        : unselectedColor))),
        BottomNavigationBarItem(
            icon: Icon(Icons.settings,
                color: _selectedIndex == 3 ? selectedColor : unselectedColor),
            title: Text('Settings',
                style: new TextStyle(
                    color: _selectedIndex == 3
                        ? selectedColor
                        : unselectedColor)))
      ],
    );
  

更新显示页面状态的方法。

  void _onItemTapped(int index) 
    _changeDisplayedScreen(index);

    setState(() 
      _selectedIndex = index;
    );

  

  void _changeDisplayedScreen(int index) 
    switch (index) 
      case 0:
        setState(() 
          _displayedPage = new LatestReadingPage();
        );
        break;
      case 1:
        setState(() 
          _displayedPage = new HomeScreen();
          //Placeholder
        );
        break;
      case 2:
        setState(() 
          _displayedPage = new HomeScreen();
          //Placeholder
        );
        break;
      case 3:
        setState(() 
          _displayedPage = new HomeScreen();
          //Placeholder
        );
        break;
      default:
        setState(() 
          _displayedPage = new LatestReadingPage();
        );
        break;
    
  

我想要的是能够使用 Flutter Navigation 基础设施,但只在更改页面时更新 Scaffold 小部件的 body 属性。而不是整个屏幕。

很像 Youtube 应用或 Google 新闻应用。

【问题讨论】:

你想出解决办法了吗? 不是,我实现了一个解决方法。我抓住了“返回”事件,并维护了一个自定义历史列表。然后我会在页面更改时更新。 【参考方案1】:

我已为您链接的帖子添加了答案:https://***.com/a/59133502/6064621

下面的答案和上面的类似,不过我这里也添加了未知路线:


您想要的可以通过使用自定义Navigator 来实现。

Flutter 团队对此做了一个video,他们关注的文章在这里:https://medium.com/flutter/getting-to-the-bottom-of-navigation-in-flutter-b3e440b9386

基本上,您需要将 Scaffold 的主体包裹在自定义 Navigator

class _MainScreenState extends State<MainScreen> 
  final _navigatorKey = GlobalKey<NavigatorState>();

  // ...

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Navigator(
        key: _navigatorKey,
        initialRoute: '/',
        onGenerateRoute: (RouteSettings settings) 
          WidgetBuilder builder;
          // Manage your route names here
          switch (settings.name) 
            case '/':
              builder = (BuildContext context) => HomePage();
              break;
            case '/page1':
              builder = (BuildContext context) => Page1();
              break;
            case '/page2':
              builder = (BuildContext context) => Page2();
              break;
            default:
              throw Exception('Invalid route: $settings.name');
          
          // You can also return a PageRouteBuilder and
          // define custom transitions between pages
          return MaterialPageRoute(
            builder: builder,
            settings: settings,
          );
        ,
      ),
      bottomNavigationBar: _yourBottomNavigationBar,
    );
  

在底部导航栏中,要导航到新自定义 Navigator 中的新屏幕,您只需调用以下代码:

_navigatorKey.currentState.pushNamed('/yourRouteName');

如果您不使用命名路由,那么您应该为自定义 Navigator 执行以下操作,并导航到新屏幕:

// Replace the above onGenerateRoute function with this one
onGenerateRoute: (RouteSettings settings) 
  return MaterialPageRoute(
    builder: (BuildContext context) => YourHomePage(),
    settings: settings,
  );
,
_navigatorKey.currentState.push(MaterialPageRoute(
  builder: (BuildContext context) => YourNextPage(),
));

要让Navigator.pop 带您到上一个视图,您需要用WillPopScope 包装自定义Navigator

@override
Widget build(BuildContext context) 
  return Scaffold(
    body: WillPopScope(
      onWillPop: () async 
        if (_navigatorKey.currentState.canPop()) 
          _navigatorKey.currentState.pop();
          return false;
        
        return true;
      ,
      child: Navigator(
        // ...
      ),
    ),
    bottomNavigationBar: _yourBottomNavigationBar,
  );

应该就是这样!无需手动处理太多弹出或管理自定义历史列表。

【讨论】:

很高兴偶然发现了这个答案。谢谢。【参考方案2】:

将你的身体包裹在 IndexedStack 小部件中。像这样:

body: IndexedStack(
      index: selectedIndex,
      children: _children, //define a  widget children list
    ),

这是一个链接https://medium.com/flutter/getting-to-the-bottom-of-navigation-in-flutter-b3e440b9386

【讨论】:

【参考方案3】:

你试过了吗?

    return Scaffold(
      backgroundColor: Colors.white,
      appBar: EmptyAppBar(),
      body: _displayedPage[_selectedIndex],
      bottomNavigationBar: _createBottomNavigationbar,
    );

然后你制作一个包含所有你想要的屏幕的小部件列表:

List<Widget> _displayedPage = [LatestReadingPage(), HomeScreen(), ExampleScreen()];

【讨论】:

如果您的_displayedPage“列表”包含无状态小部件,这将起作用。

以上是关于使用 Flutter 导航和路由时如何保持 BottomNavigationBar的主要内容,如果未能解决你的问题,请参考以下文章

11Flutter--路由和导航

Flutter Web 中的路由有意外行为

Flutter:如何避免在现有路由上推送相同的路由

Flutter 导航,必填参数,单独的路由文件,如何正确编码

更改浏览器 Url Flutter 时无法导航到初始路由错误

Flutter:如何在对话框中使用路由器