如何在 Flutter 中进行嵌套导航

Posted

技术标签:

【中文标题】如何在 Flutter 中进行嵌套导航【英文标题】:How to do nested navigation in Flutter 【发布时间】:2019-09-07 00:46:21 【问题描述】:

对于在 Flutter 中解决嵌套导航有什么建议吗?

我想要的是保持一个持久的 BottomNavigationBar,即使在重定向到新屏幕时也是如此。与 YouTube 类似,底部栏始终存在,即使您更深入地浏览菜单也是如此。

我无法从文档中弄清楚。

到目前为止,我能找到的唯一深入了解我的要求的教程是https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf(source code)。但是,它超级令人困惑。

我现在正在使用

Navigator.push(context,
                MaterialPageRoute(builder: (BuildContext context) 
              return Container()

但是,它只是将新的小部件推到整个堆栈上,覆盖了 BottomNavigationBar。

任何提示将不胜感激!

【问题讨论】:

【参考方案1】:

这是一个简单的例子,它甚至支持使用标签栏弹出到第一个屏幕。

import 'package:flutter/material.dart';

import '../library/screen.dart';
import '../playlists/screen.dart';
import '../search/screen.dart';
import '../settings/screen.dart';

class TabsScreen extends StatefulWidget 
  @override
  _TabsScreenState createState() => _TabsScreenState();


class _TabsScreenState extends State<TabsScreen> 
  int _currentIndex = 0;

  final _libraryScreen = GlobalKey<NavigatorState>();
  final _playlistScreen = GlobalKey<NavigatorState>();
  final _searchScreen = GlobalKey<NavigatorState>();
  final _settingsScreen = GlobalKey<NavigatorState>();

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: IndexedStack(
        index: _currentIndex,
        children: <Widget>[
          Navigator(
            key: _libraryScreen,
            onGenerateRoute: (route) => MaterialPageRoute(
              settings: route,
              builder: (context) => LibraryScreen(),
            ),
          ),
          Navigator(
            key: _playlistScreen,
            onGenerateRoute: (route) => MaterialPageRoute(
              settings: route,
              builder: (context) => PlaylistsScreen(),
            ),
          ),
          Navigator(
            key: _searchScreen,
            onGenerateRoute: (route) => MaterialPageRoute(
              settings: route,
              builder: (context) => SearchScreen(),
            ),
          ),
          Navigator(
            key: _settingsScreen,
            onGenerateRoute: (route) => MaterialPageRoute(
              settings: route,
              builder: (context) => SettingsScreen(),
            ),
          ),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        currentIndex: _currentIndex,
        onTap: (val) => _onTap(val, context),
        backgroundColor: Theme.of(context).scaffoldBackgroundColor,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.library_books),
            title: Text('Library'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.list),
            title: Text('Playlists'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            title: Text('Search'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            title: Text('Settings'),
          ),
        ],
      ),
    );
  

  void _onTap(int val, BuildContext context) 
    if (_currentIndex == val) 
      switch (val) 
        case 0:
          _libraryScreen.currentState.popUntil((route) => route.isFirst);
          break;
        case 1:
          _playlistScreen.currentState.popUntil((route) => route.isFirst);
          break;
        case 2:
          _searchScreen.currentState.popUntil((route) => route.isFirst);
          break;
        case 3:
          _settingsScreen.currentState.popUntil((route) => route.isFirst);
          break;
        default:
      
     else 
      if (mounted) 
        setState(() 
          _currentIndex = val;
        );
      
    
  


【讨论】:

这对我来说效果很好 - 我 generalized this approach 并添加了一些帮助者,例如 tabNavigatorOf(context, int tabIndex ) 我在保持选项卡状态时遇到问题,但索引堆栈工作得很好,谢谢 这样做后,如何从任何内页推送一个没有底栏的页面? 我在使用这种方法时遇到了问题 - 当我从一个选项卡切换到另一个选项卡然后点击返回按钮时,应用程序会关闭而不是切换回第一个选项卡。另外,如果我进行嵌套导航,例如使用导航器切换到另一个页面,例如图书馆页面,回击也会关闭应用程序。有什么建议吗? 这种方法导致的问题是点击状态栏不会滚动到顶部。【参考方案2】:

这里是持久的 BottomNavigationBar 作为启动器的示例代码:

import 'package:flutter/material.dart';

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      home: MainPage(),
    );
  


class MainPage extends StatelessWidget 
  final navigatorKey = GlobalKey<NavigatorState>();

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: Column(
        children: <Widget>[
          Expanded(
            child: Navigator(
              key: navigatorKey,
              onGenerateRoute: (route) => MaterialPageRoute(
                    settings: route,
                    builder: (context) => PageOne(),
                  ),
            ),
          ),
          BottomNavigationBar(navigatorKey)
        ],
      ),
    );
  


class BottomNavigationBar extends StatelessWidget 
  final GlobalKey<NavigatorState> navigatorKey;

  BottomNavigationBar(this.navigatorKey) : assert(navigatorKey != null);

  Future<void> push(Route route) 
    return navigatorKey.currentState.push(route);
  

  @override
  Widget build(BuildContext context) 
    return Container(
      color: Colors.blue,
      child: ButtonBar(
        alignment: MainAxisAlignment.spaceEvenly,
        children: <Widget>[
          RaisedButton(
            child: Text("PageOne"),
            onPressed: () 
              push(MaterialPageRoute(builder: (context) => PageOne()));
            ,
          ),
          RaisedButton(
            child: Text("PageTwo"),
            onPressed: () 
              push(MaterialPageRoute(builder: (context) => PageTwo()));
            ,
          )
        ],
      ),
    );
  


class PageOne extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Container(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("Page One"),
          RaisedButton(
            onPressed: ()
              Navigator.of(context).pop();
            ,
            child: Text("Pop"),
          ),
        ],
      ),
    );
  


class PageTwo extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return Container(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("Page Two"),
          RaisedButton(
            onPressed: ()
              Navigator.of(context).pop();
            ,
            child: Text("Pop"),
          ),
        ],
      ),
    );
  

屏幕录像是这样的

【讨论】:

如果我遗漏了什么,请告诉我。 我根据您的解决方案尝试了一些方法。我在Scaffold 正文中创建了一个Expanded Column。我在里面放了一个BottomNavigationBar,我正在使用GlobalKey&lt;NavigatorState&gt;()来推送新页面。但是,BottomNavigationBar 页面之间的动画非常不流畅。它没有使用BottomNavigationBar 动画。它只是将一页堆叠在另一页之上(并弄乱了go back 我不明白,你想要什么动画?左右滑动切换? @HarshBhikadia 我尝试了您的解决方案并且效果很好,但是在特定页面上时,例如Page1 和 Page1 有一个 TextField() 小部件,当单击该 TextField 小部件时,它会自动打开默认路由“/”。基本上,它会重新打开默认路由返回的页面。有什么办法解决这个问题? 似乎嵌套导航器在热重载时失去了状态,我从初始路线开始。还有其他人有这种行为吗?开发时非常讨厌......

以上是关于如何在 Flutter 中进行嵌套导航的主要内容,如果未能解决你的问题,请参考以下文章

如何在 List 小部件 Flutter 中无上下文导航

如何对嵌套的ObjectData Flutter进行排序

Flutter 嵌套声明式导航

Flutter - 底部导航 - 如何重建页面?

Flutter BLoC 模式 - 如何在流事件后导航到另一个屏幕?

如何使用嵌套的 navGraph 和底部导航视图进行导航