Flutter递归使用Navigator.push时如何保留AppBar与底部导航栏
解决Flutter树形ListView导航时底部导航栏消失/缺少Material组件的问题
方案一:在当前页面维护层级栈(推荐)
不用Navigator跳转页面,而是通过维护层级栈动态切换列表数据,底部导航栏会始终保留。
实现代码
class TreePage extends StatefulWidget { @override State<TreePage> createState() => _TreePageState(); } class _TreePageState extends State<TreePage> { // 解析后的树形数据 final List<Map<String, dynamic>> _rootData = [ { "label": "All", "children": [ {"label": "test1", "children": [{"label": "test1-1"}]}, {"label": "test2"}, {"label": "test3"} ] } ]; // 层级栈:保存每一层的列表数据 late List<List<Map<String, dynamic>>> _levelStack; // 当前显示的列表数据 late List<Map<String, dynamic>> _currentList; @override void initState() { super.initState(); // 初始化:根层级入栈,默认显示根列表 _levelStack = [_rootData]; _currentList = _rootData; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_getCurrentTitle()), // 非根层级显示返回按钮 leading: _levelStack.length > 1 ? IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { setState(() { _levelStack.removeLast(); _currentList = _levelStack.last; }); }, ) : null, ), body: ListView.separated( shrinkWrap: true, itemBuilder: (context, index) { final item = _currentList[index]; final hasChildren = item.containsKey("children") && item["children"] != null && (item["children"] as List).isNotEmpty; return ListTile( title: Text(item["label"]), trailing: hasChildren ? const Icon(Icons.arrow_forward_ios, size: 16) : null, onTap: () { if (hasChildren) { setState(() { // 子节点列表入栈,更新当前显示数据 _levelStack.add(item["children"] as List<Map<String, dynamic>>); _currentList = item["children"] as List<Map<String, dynamic>>; }); } else { // 无子节点时的处理逻辑(如跳转详情页) } }, ); }, separatorBuilder: (_, __) => const Divider(), itemCount: _currentList.length, ), bottomNavigationBar: BottomNavigationBar( items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"), BottomNavigationBarItem(icon: Icon(Icons.settings), label: "设置"), ], ), ); } // 获取当前层级的标题 String _getCurrentTitle() { if (_levelStack.length == 1) { return _currentList[0]["label"]; } else { return _currentList.isNotEmpty ? _currentList[0]["label"] : ""; } } }
方案二:使用嵌套Navigator保留底部导航栏
如果必须用Navigator.push跳转,可在底部导航栏页面中嵌套一个独立Navigator,跳转时仅更新嵌套区域,底部导航栏保持显示。
实现代码
- 底部导航栏主页面:
class HomePage extends StatefulWidget { @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { final GlobalKey<NavigatorState> _nestedNavKey = GlobalKey(); @override Widget build(BuildContext context) { return Scaffold( bottomNavigationBar: BottomNavigationBar( items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"), BottomNavigationBarItem(icon: Icon(Icons.settings), label: "设置"), ], ), body: Navigator( key: _nestedNavKey, initialRoute: "/", onGenerateRoute: (settings) { if (settings.routeName == "/") { return MaterialPageRoute( builder: (context) => const TreeSubPage( data: [ { "label": "All", "children": [ {"label": "test1", "children": [{"label": "test1-1"}]}, {"label": "test2"}, {"label": "test3"} ] } ], title: "All", ), ); } final args = settings.arguments as Map<String, dynamic>; return PageRouteBuilder( pageBuilder: (context, _, __) => TreeSubPage( data: args["data"], title: args["title"], ), transitionDuration: Duration.zero, reverseTransitionDuration: Duration.zero, ); }, ), ); } }
- 树形列表子页面:
class TreeSubPage extends StatelessWidget { final List<Map<String, dynamic>> data; final String title; const TreeSubPage({required this.data, required this.title, Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(title)), body: ListView.separated( shrinkWrap: true, itemBuilder: (context, index) { final item = data[index]; final hasChildren = item.containsKey("children") && item["children"] != null && (item["children"] as List).isNotEmpty; return ListTile( title: Text(item["label"]), trailing: hasChildren ? const Icon(Icons.arrow_forward_ios, size: 16) : null, onTap: () { if (hasChildren) { Navigator.push( context, PageRouteBuilder( pageBuilder: (context, _, __) => TreeSubPage( data: item["children"] as List<Map<String, dynamic>>, title: item["label"], ), transitionDuration: Duration.zero, reverseTransitionDuration: Duration.zero, ), ); } else { // 无子节点的处理逻辑 } }, ); }, separatorBuilder: (_, __) => const Divider(), itemCount: data.length, ), ); } }
问题根源说明
- 直接
Navigator.push新Scaffold会替换整个页面(包括底部导航栏所在的父Scaffold),导致底部导航栏消失。 - 直接
Navigator.push当前类(如CurrentClass)时,若该类未嵌套在Material组件树中,ListTile等组件会因缺少Material上下文报错。
内容的提问来源于stack exchange,提问作者Kate Jo
相关产品推荐
相关产品推荐

