能否用GoRouter实现带BottomNavBar的PageView功能?
你的思路完全可行
借助PageView.builder配合ShellRoute的itemBuilder是GoRouter实现这类底部导航布局的标准方案之一,完美适配动态生成页面的需求,不需要预先传入所有children,完全可以落地。
具体实现步骤
1. 配置路由结构
先定义底部导航对应的子路由,再通过ShellRoute包裹这些路由,统一管理底部导航栏和PageView容器:
final router = GoRouter( initialLocation: '/home', routes: [ ShellRoute( builder: (context, state, child) { return RootShell(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(), ), ], ), ], );
2. 实现RootShell组件
在Shell组件中集成BottomNavigationBar和PageView.builder,通过ShellRouteState动态获取子路由页面:
class RootShell extends StatelessWidget { const RootShell({super.key, required this.state}); final ShellRouteState state; @override Widget build(BuildContext context) { // 定义底部导航项与路由的映射 final tabItems = [ (icon: Icons.home, label: '首页', route: state.routeList[0]), (icon: Icons.person, label: '我的', route: state.routeList[1]), (icon: Icons.settings, label: '设置', route: state.routeList[2]), ]; // 获取当前路由对应的索引 final currentIndex = tabItems.indexWhere( (item) => item.route.location == state.location, ); return Scaffold( body: PageView.builder( itemCount: tabItems.length, itemBuilder: (context, index) { // 通过ShellRouteState生成对应路由的页面 return tabItems[index].route.builder(context, state); }, // 若要禁止手动滑动切换,可添加此属性;保留滑动则删除 physics: const NeverScrollableScrollPhysics(), ), bottomNavigationBar: BottomNavigationBar( currentIndex: currentIndex, type: BottomNavigationBarType.fixed, items: tabItems.map((item) { return BottomNavigationBarItem( icon: Icon(item.icon), label: item.label, ); }).toList(), onTap: (index) { // 点击导航项切换路由 context.go(tabItems[index].route.location!); }, ), ); } }
其他可选方案
方案一:手动管理索引与路由同步
无需ShellRoute,直接用StatefulWidget管理PageController,监听路由变化同步页面索引:
class RootPage extends StatefulWidget { const RootPage({super.key}); @override State<RootPage> createState() => _RootPageState(); } class _RootPageState extends State<RootPage> { final PageController _pageController = PageController(); final _routePaths = ['/home', '/profile', '/settings']; int _currentIndex = 0; @override void initState() { super.initState(); // 监听路由变化更新页面索引 GoRouter.of(context).addListener(_syncRouteAndPage); } @override void dispose() { GoRouter.of(context).removeListener(_syncRouteAndPage); _pageController.dispose(); super.dispose(); } void _syncRouteAndPage() { final currentPath = GoRouter.of(context).location; final index = _routePaths.indexOf(currentPath); if (index != -1 && index != _currentIndex) { setState(() => _currentIndex = index); _pageController.jumpToPage(index); } } @override Widget build(BuildContext context) { return Scaffold( body: PageView( controller: _pageController, onPageChanged: (index) { // 滑动页面时同步路由 if (index != _currentIndex) { context.go(_routePaths[index]); } }, children: const [ HomePage(), ProfilePage(), SettingsPage(), ], ), bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'), BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'), BottomNavigationBarItem(icon: Icons.settings, label: '设置'), ], onTap: (index) { setState(() => _currentIndex = index); _pageController.jumpToPage(index); context.go(_routePaths[index]); }, ), ); } }
方案二:基于GoRouter的refreshListenable同步状态
通过自定义ChangeNotifier监听路由变化,同步BottomNavBar和PageView的状态,适合更复杂的状态管理场景。
内容的提问来源于stack exchange,提问作者Karol Wiśniewski
相关产品推荐
相关产品推荐

