如何结合NavigationRail实现Flutter GoRouter的嵌套路由与页面切换
看了你贴的代码,其实已经把核心框架搭得差不多了,只需要几个关键调整,就能实现点击NavigationRail时路由同步变化(比如/landing/home)同时页面切换的效果,咱们一步步来改:
一、给ShellRoute绑定基础路径
你现在的ShellRoute没有指定path,导致子路由的路径是/home而不是/landing/home。先给ShellRoute加上/landing前缀,让所有子路由自动继承这个路径:
修改GoRouter里的ShellRoute部分:
ShellRoute( navigatorKey: _landingNavigatorKey, path: Routes.landing, // 新增这一行,指定父路由路径为/landing builder: (context, state, child) => const LandingScreen(child: child), routes: [ // 子路由保持不变,现在它们的实际路径会变成/landing/home、/landing/live等 GoRoute( path: Routes.home, name: Routes.home, builder: (_, __) => const HomeScreen(), ), // 其他子路由(live、tvGuide等)同理... ], ),
同时要确保你的Routes类里定义了这个路径:
class Routes { // ...其他已有定义 static const landing = '/landing'; }
二、建立NavigationRail索引与路由的映射
在LandingScreen的状态类里,新增一个列表,把每个RailItem对应的路由名称按顺序存起来,和你的NavigationRailDestination顺序完全对应:
class _LandingScreenState extends ConsumerState<LandingScreen> { // 新增:索引对应的路由名称列表,顺序要和Rail的destinations完全一致 final List<String> _routeNames = [ Routes.home, Routes.live, Routes.tvGuide, Routes.weekly, Routes.recordings, Routes.settings, ]; // 其他原有代码(_iconWidth、_railItemPadding等)... }
三、修改Rail点击逻辑:路由跳转+状态同步
原来的onDestinationSelected只更新了状态索引,没触发路由跳转,所以路径不会变。现在改成点击时同时跳转路由并更新状态:
onDestinationSelected: (selectedIndex) { // 1. 更新状态管理的选中索引 ref.read(landingIndexProvider.notifier).updateIndex(selectedIndex); // 2. 跳转到对应的路由 context.goNamed(_routeNames[selectedIndex]); },
四、同步路由变化到Rail选中状态
为了避免用户直接输入URL(或其他方式跳转)时,Rail的选中索引和路由不一致,需要在build方法里监听当前路由,自动同步索引:
@override Widget build(BuildContext context) { // 新增:监听当前路由,同步更新Rail选中索引 final currentRouteName = GoRouterState.of(context).name; final currentIndex = _routeNames.indexOf(currentRouteName); if (currentIndex != -1 && currentIndex != ref.read(landingIndexProvider)) { ref.read(landingIndexProvider.notifier).updateIndex(currentIndex); } // 原有代码:获取当前索引、profile名称等 final index = ref.watch(landingIndexProvider); final currentProfileName = ref.watch(currentProfileStateProvider); ref.watch(asyncProfilesProvider(currentProfileName)); // ...后续Scaffold构建代码不变 }
五、修正ProfileSelecting的跳转逻辑
原来的redirect直接返回Routes.landing,但/landing是ShellRoute,需要跳转到它的默认子路由(比如/landing/home)。修改ProfileSelecting的GoRoute:
GoRoute( path: Routes.profileSelecting, name: Routes.profileSelecting, redirect: (_, state) async { final stbAccount = await ref.read(asyncAccountProvider.future); final isMultipleProfiles = stbAccount.data.profiles!.length > 1; if (isMultipleProfiles) { // 有多个profile时,停留在当前选择页面 return null; } // 无多个profile时,直接跳转到landing的home页面 return '/${Routes.landing}/${Routes.home}'; }, builder: (_, __) => const ProfileSelectionScreen(), ),
最后检查Routes类的子路由定义
确保子路由的path是相对路径(不带/),因为ShellRoute已经有了/landing前缀:
class Routes { // ...其他定义 static const home = 'home'; static const live = 'live'; static const tvGuide = 'tv-guide'; static const weekly = 'weekly'; static const recordings = 'recordings'; static const settings = 'settings'; }
这样调整后,点击NavigationRail的各个选项时,路由会自动变成/landing/home、/landing/live等,页面也会同步切换对应的子页面,同时Rail的选中状态和路由始终保持一致。
备注:内容来源于stack exchange,提问作者TeamlyDigital

