如何实现持久化的Material 3 NavigationBar组件
以下是满足你需求的简化实现方案,用原生Material3组件实现持久化底部导航栏,支持页面跳转时底部栏不消失、切换标签保留页面状态:
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Persistent NavigationBar Demo', theme: ThemeData( useMaterial3: true, colorSchemeSeed: Colors.blue, ), home: const MainPage(), ); } } class MainPage extends StatefulWidget { const MainPage({super.key}); @override State<MainPage> createState() => _MainPageState(); } class _MainPageState extends State<MainPage> { int _selectedIndex = 0; // 为每个底部标签分配独立的Navigator全局键 final List<GlobalKey<NavigatorState>> _navigatorKeys = [ GlobalKey<NavigatorState>(), GlobalKey<NavigatorState>(), GlobalKey<NavigatorState>(), ]; void _onItemTapped(int index) { setState(() { _selectedIndex = index; }); } @override Widget build(BuildContext context) { return Scaffold( // 用Stack+Offstage管理多个Navigator的显示状态 body: Stack( children: List.generate(3, (index) { return Offstage( offstage: index != _selectedIndex, child: Navigator( key: _navigatorKeys[index], onGenerateRoute: (settings) { return MaterialPageRoute( builder: (context) => _getPage(index), ); }, ), ); }), ), bottomNavigationBar: NavigationBar( selectedIndex: _selectedIndex, onDestinationSelected: _onItemTapped, destinations: const [ NavigationDestination( icon: Icon(Icons.home), label: '首页', ), NavigationDestination( icon: Icon(Icons.search), label: '搜索', ), NavigationDestination( icon: Icon(Icons.person), label: '我的', ), ], ), ); } // 返回对应标签的初始页面 Widget _getPage(int index) { switch (index) { case 0: return const HomePage(); case 1: return const SearchPage(); case 2: return const ProfilePage(); default: return const HomePage(); } } } // 首页示例(包含跳转按钮) class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('首页')), body: Center( child: ElevatedButton( onPressed: () { // 跳转使用当前标签页的Navigator上下文,不会影响底部栏 Navigator.push( context, MaterialPageRoute(builder: (context) => const DetailPage()), ); }, child: const Text('跳转到详情页'), ), ), ); } } // 详情页(带返回按钮) class DetailPage extends StatelessWidget { const DetailPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('详情页')), body: const Center(child: Text('这是详情页面,底部导航栏依然存在')), ); } } // 搜索页示例 class SearchPage extends StatelessWidget { const SearchPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('搜索页')), body: const Center(child: Text('搜索页面')), ); } } // 个人中心页示例 class ProfilePage extends StatelessWidget { const ProfilePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('我的页面')), body: const Center(child: Text('个人中心页面')), ); } }
核心实现逻辑
- 独立Navigator栈:每个底部标签对应一个带全局键的
Navigator,页面跳转仅在当前标签的子栈内进行,根页面的Scaffold(包含底部导航栏)始终保持渲染。 - Offstage状态管理:用
Stack和Offstage控制不同标签页的显示/隐藏,未选中的标签页只是隐藏而非销毁,因此页面状态会被保留。 - 上下文隔离:页面内的
Navigator.push使用当前标签页的上下文,不会触发根Navigator的页面替换,避免底部导航栏消失。
为什么之前的代码会失效
你之前的跳转代码使用了根Navigator的上下文,跳转后会替换整个根页面内容,导致底部导航栏被移除。而本方案通过子Navigator实现了页面栈的隔离,完美适配Material3的持久化导航栏要求。
内容的提问来源于stack exchange,提问作者Nick Khotenko
相关产品推荐
相关产品推荐

