Flutter Web:结合GoRouter与PageView Builder实现命名路由导航并复用Scaffold
在Flutter Web中结合GoRouter与PageView实现单Scaffold下的命名路由导航
要实现这个需求,核心是利用GoRouter的ShellRoute承载全局Scaffold,同时将PageView作为Shell的内容容器,让命名路由与PageView的页面项一一绑定,实现路由切换仅更新PageView内容,而Scaffold的全局UI保持不变。下面是完整的实现方案:
1. 依赖准备
确保你的pubspec.yaml中添加了go_router依赖:
dependencies: flutter: sdk: flutter go_router: ^12.0.0
2. 定义页面组件
创建示例页面,用StatefulWidget+PageStorage机制保持页面状态,避免切换时重初始化:
class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Center( child: Text('首页', style: Theme.of(context).textTheme.headlineLarge), ); } } class ProfilePage extends StatefulWidget { const ProfilePage({super.key}); @override State<ProfilePage> createState() => _ProfilePageState(); } class _ProfilePageState extends State<ProfilePage> { int _counter = 0; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('个人中心 - 计数: $_counter', style: Theme.of(context).textTheme.headlineMedium), ElevatedButton( onPressed: () => setState(() => _counter++), child: const Text('增加计数'), ), ], ), ); } } class SettingsPage extends StatelessWidget { const SettingsPage({super.key}); @override Widget build(BuildContext context) { return Center( child: Text('设置页', style: Theme.of(context).textTheme.headlineLarge), ); } }
3. 配置GoRouter与ShellRoute
用ShellRoute作为根路由承载全局Scaffold,子路由对应PageView的各个页面:
final _router = GoRouter( initialLocation: '/home', routes: [ ShellRoute( builder: (context, state, child) => ShellWidget(state: state), routes: [ GoRoute( path: '/home', builder: (context, state) => const HomePage(), ), GoRoute( path: '/profile', builder: (context, state) => const ProfilePage(), ), GoRoute( path: '/settings', builder: (context, state) => const SettingsPage(), ), ], ), ], );
4. 实现ShellWidget(单Scaffold+PageView)
这个Widget是全局Scaffold的载体,负责路由与PageView的双向同步:
class ShellWidget extends StatefulWidget { final GoRouterState state; const ShellWidget({super.key, required this.state}); @override State<ShellWidget> createState() => _ShellWidgetState(); } class _ShellWidgetState extends State<ShellWidget> { late final PageController _pageController; int _currentIndex = 0; // 路由路径与PageView索引的映射 final Map<String, int> _routeIndexMap = { '/home': 0, '/profile': 1, '/settings': 2, }; // 索引对应的路由路径 final List<String> _indexRouteMap = [ '/home', '/profile', '/settings', ]; @override void initState() { super.initState(); _currentIndex = _routeIndexMap[widget.state.location] ?? 0; _pageController = PageController(initialPage: _currentIndex); // 监听路由变化,更新PageView索引 GoRouter.of(context).addListener(_onRouteChanged); } @override void dispose() { GoRouter.of(context).removeListener(_onRouteChanged); _pageController.dispose(); super.dispose(); } void _onRouteChanged() { final location = GoRouter.of(context).location; final index = _routeIndexMap[location] ?? 0; if (_currentIndex != index) { setState(() => _currentIndex = index); _pageController.jumpToPage(index); } } void _onPageChanged(int index) { if (_currentIndex != index) { setState(() => _currentIndex = index); // 切换路由,保证URL与页面同步 GoRouter.of(context).go(_indexRouteMap[index]); } } Widget _buildPage(int index) { switch (index) { case 0: return const HomePage(); case 1: return const ProfilePage(); case 2: return const SettingsPage(); default: return const SizedBox.shrink(); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('全局导航栏')), body: PageView.builder( controller: _pageController, // 不需要滑动切换可设置为NeverScrollableScrollPhysics,否则移除 physics: const NeverScrollableScrollPhysics(), onPageChanged: _onPageChanged, itemCount: _indexRouteMap.length, itemBuilder: (context, index) => _buildPage(index), ), bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, onTap: _onPageChanged, items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'), BottomNavigationBarItem(icon: Icon(Icons.person), label: '个人中心'), BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'), ], ), ); } }
5. 初始化应用
用MaterialApp.router集成GoRouter:
void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Flutter Web 单Scaffold导航', theme: ThemeData(primarySwatch: Colors.blue), routerConfig: _router, ); } }
关键说明
- ShellRoute的作用:作为根路由承载全局Scaffold,确保Scaffold只渲染一次,子路由内容通过PageView切换,不会触发Scaffold重建。
- 双向同步:监听GoRouter路由变化更新PageView索引,同时PageView切换时更新路由,保证URL与页面内容一致。
- 状态保持:利用PageView默认的
PageStorage机制,结合StatefulWidget,页面切换时不会丢失临时状态(比如ProfilePage的计数)。 - Web适配:GoRouter自动处理浏览器前进后退的路由历史,无需额外配置。
内容的提问来源于stack exchange,提问作者Salman Ali
相关产品推荐
相关产品推荐

