如何在自定义PageRouteBuilder中保留旧Widget状态?
问题描述
我自定义了PageRouteBuilder实现页面过渡动画,当前动画能正常运行,但旧Widget在动画过程中会重加载,体验不佳。尝试过使用secondaryAnimation参数实现动画,但灵活性不足,无法自由控制动画的触发时机与方向。现需解决两个核心需求:
- 自主控制动画的左右滑动方向
- 避免旧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
相关产品推荐
相关产品推荐

