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

如何在自定义PageRouteBuilder中保留旧Widget状态?

问题描述

我自定义了PageRouteBuilder实现页面过渡动画,当前动画能正常运行,但旧Widget在动画过程中会重加载,体验不佳。尝试过使用secondaryAnimation参数实现动画,但灵活性不足,无法自由控制动画的触发时机与方向。现需解决两个核心需求:

  1. 自主控制动画的左右滑动方向
  2. 避免旧Widget在动画时重加载,同时通过NavigatorState传递当前Widget或其状态给PageRouteBuilder

现有代码

自定义MyRouter实现

class MyRouter extends PageRouteBuilder {
  final String? transitionType;
  final Widget? widget;
  final Widget? leavingWidget;
  final RouteSettings? routeSettings;

  MyRouter(
      {this.transitionType,
      this.widget,
      this.leavingWidget,
      this.routeSettings})
      : super(
          settings: routeSettings,
          //transitionDuration: const Duration(seconds: 3),
          pageBuilder: (context, animation, secondaryAnimation) => widget!,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {

      } else if (transitionType == 'slideLeftOld') {
             return Stack(
                      children: [
                        SlideTransition(
                          position: Tween(
                            begin: Offset.zero,
                            end: Offset(-1.0, 0.0),
                          ).animate(animation),
                          child: leavingWidget,
                        ),
                        SlideTransition(
                          position: Tween(
                            begin: Offset(1.0, 0.0),
                            end: Offset.zero,
                          ).animate(animation),
                          child: widget,
                        ),
                      ],
                    );
  }
}

路由生成逻辑

Route _onGenerateRoute(RouteSettings settings) {
    final args = settings.arguments;

    switch (settings.name) {
      case '/slideLeftOld':
        return MyRouter(
            routeSettings: RouteSettings(name: settings.name),
            widget: CalendarSwiperContent(
              selectedDay: args as DateTime,
              changeItems: _callbackItems,
            ),
            leavingWidget: getCurrentWidget(),
            transitionType: 'slideLeftOld');
      // 其他路由分支
    }
}

路由跳转代码

await routineNavigatorKey.currentState!
                  .pushReplacementNamed('/slideLeftOld', arguments: dateTime);

当前动画效果

左右滑动过渡动画


解决方案

1. 避免旧Widget重加载:缓存当前页面实例

问题根源是getCurrentWidget()每次调用都会重新创建Widget实例,导致动画过程中旧Widget重建。可通过以下方式缓存:

方式一:直接获取当前页面的Widget实例

在跳转前通过Navigator的上下文获取已挂载的当前页面Widget,避免重建:

// 跳转时获取当前页面Widget
final currentContext = routineNavigatorKey.currentState!.context;
final currentPageWidget = (currentContext as Element).widget;

await routineNavigatorKey.currentState!.pushReplacementNamed(
  '/slideLeftOld',
  arguments: {
    'dateTime': dateTime,
    'leavingWidget': currentPageWidget,
  },
);

在路由生成方法中取出缓存的Widget:

case '/slideLeftOld':
  final args = settings.arguments as Map;
  return MyRouter(
    routeSettings: RouteSettings(name: settings.name),
    widget: CalendarSwiperContent(
      selectedDay: args['dateTime'] as DateTime,
      changeItems: _callbackItems,
    ),
    leavingWidget: args['leavingWidget'] as Widget,
    transitionType: 'slideLeftOld',
  );

方式二:状态管理工具缓存页面状态

如果页面包含需要保留的状态,可使用Provider、GetX等状态管理工具缓存页面的状态实例,或者给页面添加PageStorageKey标记,避免重建时丢失状态。

2. 自主控制动画方向:扩展动画参数

在MyRouter中新增控制方向的参数,动态调整动画偏移量:

class MyRouter extends PageRouteBuilder {
  final String? transitionType;
  final Widget? widget;
  final Widget? leavingWidget;
  final RouteSettings? routeSettings;
  final bool isReverse; // 新增:控制动画方向,默认正向

  MyRouter({
    this.transitionType,
    this.widget,
    this.leavingWidget,
    this.routeSettings,
    this.isReverse = false,
  }) : super(
          settings: routeSettings,
          pageBuilder: (context, animation, secondaryAnimation) => widget!,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            if (transitionType == 'slideLeftOld') {
              // 根据isReverse切换偏移方向
              final leavingOffset = isReverse 
                  ? Offset(1.0, 0.0) 
                  : Offset(-1.0, 0.0);
              final enteringOffset = isReverse 
                  ? Offset(-1.0, 0.0) 
                  : Offset(1.0, 0.0);

              return Stack(
                children: [
                  SlideTransition(
                    position: Tween(
                      begin: Offset.zero,
                      end: leavingOffset,
                    ).animate(animation),
                    child: leavingWidget,
                  ),
                  SlideTransition(
                    position: Tween(
                      begin: enteringOffset,
                      end: Offset.zero,
                    ).animate(animation),
                    child: widget,
                  ),
                ],
              );
            }
            return child;
          },
        );
}

使用时传入isReverse即可切换动画方向:

return MyRouter(
  // 其他参数
  isReverse: true, // 开启反向动画:旧页面右滑退出,新页面左滑进入
);

3. 精细控制动画触发时机:自定义动画控制器

如果需要完全掌控动画的启动、暂停、反向时机,可在MyRouter中自定义AnimationController:

class MyRouter extends PageRouteBuilder {
  final String? transitionType;
  final Widget? widget;
  final Widget? leavingWidget;
  final RouteSettings? routeSettings;
  final bool isReverse;
  late final AnimationController _controller;

  MyRouter({
    this.transitionType,
    this.widget,
    this.leavingWidget,
    this.routeSettings,
    this.isReverse = false,
    Duration transitionDuration = const Duration(milliseconds: 300),
  }) : super(
          settings: routeSettings,
          transitionDuration: transitionDuration,
          pageBuilder: (context, animation, secondaryAnimation) => widget!,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            if (transitionType == 'slideLeftOld') {
              final leavingOffset = isReverse 
                  ? Offset(1.0, 0.0) 
                  : Offset(-1.0, 0.0);
              final enteringOffset = isReverse 
                  ? Offset(-1.0, 0.0) 
                  : Offset(1.0, 0.0);

              return Stack(
                children: [
                  SlideTransition(
                    position: Tween(
                      begin: Offset.zero,
                      end: leavingOffset,
                    ).animate(_controller),
                    child: leavingWidget,
                  ),
                  SlideTransition(
                    position: Tween(
                      begin: enteringOffset,
                      end: Offset.zero,
                    ).animate(_controller),
                    child: widget,
                  ),
                ],
              );
            }
            return child;
          },
        ) {
    // 初始化自定义控制器
    _controller = AnimationController(
      duration: transitionDuration,
      vsync: Navigator.of(routeSettings!.context!),
    );
    // 自动启动动画(也可手动调用_controller.forward()控制时机)
    _controller.forward();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

通过_controller.forward()、_controller.reverse()等方法,可完全自定义动画的触发时机与状态。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 02:24:55