如何重建Widget树切换路由包装器?实现auto_route响应式页面状态恢复
解决方案:基于AutoRoute实现响应式主/详情视图的状态恢复
核心思路
通过统一路由配置、响应式路由守卫、动态根路由切换三个核心环节,实现模式(桌面/移动端)切换时的路由路径保留与状态恢复:
- 统一注册独立的列表页、详情页路由,分屏页作为嵌套路由容器
- 用路由守卫根据当前设备模式动态调整路由跳转逻辑
- 监听窗口尺寸变化,自动切换根路由的渲染模式(分屏/栈)
步骤1:统一路由配置
避免硬编码列表页,将所有页面注册为独立路由,分屏页作为嵌套路由容器,确保路径一致性:
import 'package:auto_route/auto_route.dart'; @MaterialAutoRouter( replaceInRouteName: 'Page,Route', routes: [ // 列表页路由 AutoRoute(path: '/', page: HomeListPage, guards: [ResponsiveGuard()]), // 详情页路由 AutoRoute(path: '/detail/:id', page: DetailPage, guards: [ResponsiveGuard()]), // 分屏容器路由,包含列表+详情的嵌套路由 AutoRoute( path: '/split-screen', page: SplitScreenPage, guards: [ResponsiveGuard()], children: [ AutoRoute(path: '', page: HomeListPage), AutoRoute(path: 'detail/:id', page: DetailPage), ], ), ], ) class $AppRouter {}
步骤2:实现响应式路由守卫
创建路由守卫,根据当前设备尺寸判断模式,动态处理路由跳转逻辑,确保模式切换时栈状态正确:
class ResponsiveGuard extends AutoRouteGuard { // 定义桌面/移动端的尺寸阈值 static const desktopThreshold = 600.0; @override void onNavigation(NavigationResolver resolver, StackRouter router) { final context = router.navigatorKey.currentContext!; final isDesktop = MediaQuery.of(context).size.width > desktopThreshold; final routeName = resolver.route.name; // 处理列表页路由 if (routeName == HomeListRoute.name) { if (isDesktop) { // 桌面模式下跳转到分屏容器,默认显示列表 router.push(const SplitScreenRoute(children: [HomeListRoute()])); resolver.next(false); } else { resolver.next(true); } return; } // 处理详情页路由 if (routeName == DetailRoute.name) { final id = resolver.pathParams.get('id')!; if (isDesktop) { // 桌面模式下跳转到分屏容器,同时加载列表和详情 router.push(SplitScreenRoute(children: [ const HomeListRoute(), DetailRoute(id: id), ])); resolver.next(false); } else { // 移动端模式下,确保栈中先有列表页,再推入详情页 if (!router.contains(const HomeListRoute())) { router.push(const HomeListRoute()).then((_) => resolver.next(true)); } else { resolver.next(true); } } return; } // 其他路由直接放行 resolver.next(true); } }
步骤3:实现分屏容器页面
分屏页固定左侧显示列表,右侧通过嵌套AutoRouter渲染详情内容,自动同步路由状态:
class SplitScreenPage extends StatelessWidget { const SplitScreenPage({super.key}); @override Widget build(BuildContext context) { // 获取当前分屏路由的子路由列表 final childRoutes = RouteData.of(context).children; return Row( children: [ // 左侧固定显示列表页 const Expanded( flex: 1, child: HomeListPage(), ), const VerticalDivider(width: 1), // 右侧通过AutoRouter渲染子路由(详情页) Expanded( flex: 2, child: AutoRouter(initialChildren: childRoutes), ), ], ); } }
步骤4:动态切换根路由
在应用根组件监听窗口尺寸变化,自动切换根路由的渲染模式(分屏/栈):
class AppRoot extends StatelessWidget { AppRoot({super.key}); final _appRouter = AppRouter(); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final isDesktop = constraints.maxWidth > ResponsiveGuard.desktopThreshold; return MaterialApp.router( routerDelegate: AutoRouterDelegate( _appRouter, // 桌面模式下默认路由设为分屏容器 initialRoutes: isDesktop ? [const SplitScreenRoute(children: [HomeListRoute()])] : null, ), routeInformationParser: _appRouter.defaultRouteParser(), ); }, ); } }
关键效果验证
- 桌面→移动端切换:当桌面模式路径为
/detail/5时,旋转设备到移动端,路径保持不变,栈自动恢复为「列表页→详情页5」,回退按钮可返回列表。 - 移动端→桌面切换:移动端从列表进入详情5后旋转到桌面,自动切换为分屏模式,左侧显示列表、右侧显示详情5,路径仍为
/detail/5。 - 直接访问详情路径:无论哪种模式,直接访问
/detail/5都会自动构建对应状态的视图(分屏或栈)。
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

