You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何重建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(),
        );
      },
    );
  }
}

关键效果验证

  1. 桌面→移动端切换:当桌面模式路径为/detail/5时,旋转设备到移动端,路径保持不变,栈自动恢复为「列表页→详情页5」,回退按钮可返回列表。
  2. 移动端→桌面切换:移动端从列表进入详情5后旋转到桌面,自动切换为分屏模式,左侧显示列表、右侧显示详情5,路径仍为/detail/5。
  3. 直接访问详情路径:无论哪种模式,直接访问/detail/5都会自动构建对应状态的视图(分屏或栈)。

内容的提问来源于stack exchange,提问作者Adam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 17:15:19