Flutter:实现Scaffold主体内局部导航,保留顶部及底部导航栏
如何在Flutter Scaffold主体内实现局部导航(保留顶部AppBar和底部导航栏)
需求:点击“+”按钮时,替换Scaffold的当前主体内容,同时保持顶部AppBar和底部导航栏不变。
原有代码(main.dart):
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( home: const Home(), theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.yellow, ), ), )); } class Home extends StatefulWidget { const Home({super.key}); @override State<Home> createState() => _HomeState(); } class _HomeState extends State<Home> { int _selectedIndex = 0; final List<Widget> _screens = <Widget>[ HomeScreen(), StatsScreen() ]; void _onItemTap(int index) { setState(() { _selectedIndex = index; }); } @override Widget build(BuildContext context) { return Scaffold( bottomNavigationBar: NavigationBar( labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected, selectedIndex: _selectedIndex, destinations: const <Widget>[ NavigationDestination( icon: Icon(Icons.home), label: 'Home' ), NavigationDestination( icon: Icon(Icons.trending_up), label: 'Statistics', ), ], onDestinationSelected: _onItemTap, ), body: _screens.elementAt(_selectedIndex), ); } } class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home'), actions: [ IconButton( onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (context) => SettingsScreen())); }, icon: const Icon(Icons.settings), ), ], ), body: Column( children: [ Expanded(child: Container(color: Colors.yellow)), Container( padding: EdgeInsets.all(8), alignment: Alignment.bottomRight, child: FloatingActionButton( onPressed: () {}, //need to push a new body here child: Icon(Icons.add))) ], ), ); } } // 补充缺失的页面类 class StatsScreen extends StatelessWidget { const StatsScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Statistics')), body: Center(child: Text('Statistics Screen')), ); } } class SettingsScreen extends StatelessWidget { const SettingsScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Settings')), body: Center(child: Text('Settings Screen')), ); } } class AddNewScreen extends StatelessWidget { const AddNewScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Add New')), body: Center(child: Text('Add New Screen')), ); } }
参考图:
遇到的问题:使用默认Navigator会在当前Scaffold上方叠加新页面,导致顶部AppBar和底部导航栏被覆盖;看过类似问题但答案不清晰,没有完整代码示例。
核心思路是给需要局部导航的区域单独创建一个Navigator组件,用GlobalKey管理其导航栈,这样导航操作只会影响该区域,不会覆盖外层的AppBar和底部导航栏。
修改后的完整代码:
import 'package:flutter/material.dart'; void main() { runApp(MaterialApp( home: const Home(), theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Colors.yellow, ), ), )); } class Home extends StatefulWidget { const Home({super.key}); @override State<Home> createState() => _HomeState(); } class _HomeState extends State<Home> { int _selectedIndex = 0; // 给每个底部导航页面维护独立的局部导航key final _homeNavigatorKey = GlobalKey<NavigatorState>(); final _statsNavigatorKey = GlobalKey<NavigatorState>(); void _onItemTap(int index) { setState(() { _selectedIndex = index; }); } @override Widget build(BuildContext context) { return Scaffold( // 把AppBar提到外层,确保所有底部导航页面共享统一的顶部栏(可根据选中页切换标题) appBar: AppBar( title: Text(_selectedIndex == 0 ? 'Home' : 'Statistics'), actions: [ IconButton( onPressed: () { // 根据当前选中页面,调用对应的局部Navigator打开设置页 if (_selectedIndex == 0) { _homeNavigatorKey.currentState?.push( MaterialPageRoute(builder: (context) => const SettingsScreen()), ); } else { _statsNavigatorKey.currentState?.push( MaterialPageRoute(builder: (context) => const SettingsScreen()), ); } }, icon: const Icon(Icons.settings), ), ], ), bottomNavigationBar: NavigationBar( labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected, selectedIndex: _selectedIndex, destinations: const <Widget>[ NavigationDestination( icon: Icon(Icons.home), label: 'Home', ), NavigationDestination( icon: Icon(Icons.trending_up), label: 'Statistics', ), ], onDestinationSelected: _onItemTap, ), body: IndexedStack( index: _selectedIndex, children: [ // Home页面的局部导航容器 Navigator( key: _homeNavigatorKey, onGenerateRoute: (settings) { return MaterialPageRoute( builder: (context) => const HomeContent(), ); }, ), // Statistics页面的局部导航容器 Navigator( key: _statsNavigatorKey, onGenerateRoute: (settings) { return MaterialPageRoute( builder: (context) => const StatsContent(), ); }, ), ], ), ); } } // Home页面的初始内容(不再嵌套Scaffold) class HomeContent extends StatelessWidget { const HomeContent({super.key}); @override Widget build(BuildContext context) { return Column( children: [ const Expanded(child: Container(color: Colors.yellow)), Container( padding: const EdgeInsets.all(8), alignment: Alignment.bottomRight, child: FloatingActionButton( onPressed: () { // 使用当前上下文的局部Navigator打开新页面 Navigator.push( context, MaterialPageRoute(builder: (context) => const AddNewScreen()), ); }, child: const Icon(Icons.add), ), ), ], ); } } // Statistics页面的初始内容 class StatsContent extends StatelessWidget { const StatsContent({super.key}); @override Widget build(BuildContext context) { return const Center(child: Text('Statistics Screen')); } } // 设置页面(局部导航打开) class SettingsScreen extends StatelessWidget { const SettingsScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Settings')), body: const Center(child: Text('Settings Screen')), ); } } // 点击+按钮打开的新页面 class AddNewScreen extends StatelessWidget { const AddNewScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Add New')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('这是点击+按钮打开的新页面'), ElevatedButton( onPressed: () => Navigator.pop(context), child: const Text('返回'), ), ], ), ), ); } }
关键改动说明:
- 移除嵌套Scaffold:删掉原来HomeScreen和StatsScreen里的Scaffold,将AppBar移到外层的Home Scaffold中,确保底部导航栏和顶部栏始终可见。
- 创建局部Navigator:给每个底部导航页面分配
GlobalKey<NavigatorState>,用IndexedStack包裹两个Navigator组件,每个Navigator对应独立的页面导航栈。 - 局部导航操作:点击+按钮或设置按钮时,调用对应页面的局部Navigator来push新页面,仅替换当前主体内容,外层栏位保持不变。
- IndexedStack的作用:切换底部导航时保留未选中页面的状态,避免重新初始化。
效果验证:
- 点击Home页面的+按钮,主体区域会打开AddNewScreen,顶部AppBar和底部导航栏依然存在。
- 点击返回按钮,回到HomeContent页面。
- 切换底部导航到Statistics再切回Home,页面状态会被保留。
内容的提问来源于stack exchange,提问作者void
相关产品推荐
相关产品推荐

