请求指导:在Flutter Navigation 2.0中使用TransitionDelegate自定义页面转场动画(如bounceOut)
Hey there! 作为刚上手Flutter Navigation 2.0的新手,确实会被它的声明式逻辑绕晕,尤其是转场动画这块——毕竟和1.0的命令式路由玩法差异挺大的。你遇到的问题其实非常典型:Navigation 2.0是基于Page对象而非Route对象来管理页面栈的,所以直接用MaterialPageRoute替换MaterialPage肯定行不通,因为Navigator.pages只接受Page类型。下面我给你两种可行的实现方案,帮你搞定bounceOut这类自定义转场动画:
方案一:自定义带转场的Page类(快速实现单个页面的自定义转场)
这个方案最直观,核心是让自定义Page自己生成带转场效果的Route,Navigation 2.0的Navigator会自动复用这个Route的转场逻辑。
1. 定义自定义Page类
class CustomTransitionPage<T> extends Page<T> { final Widget child; final Curve exitCurve; // 退出动画曲线,这里我们用bounceOut final Curve enterCurve; const CustomTransitionPage({ required this.child, this.exitCurve = Curves.bounceOut, this.enterCurve = Curves.linear, super.key, super.name, super.arguments, super.restorationId, }); @override Route<T> createRoute(BuildContext context) { return PageRouteBuilder<T>( settings: this, // 必须传递当前Page的settings,保证Navigation 2.0栈状态正确 transitionDuration: const Duration(milliseconds: 800), reverseTransitionDuration: const Duration(milliseconds: 800), // 页面内容构建 pageBuilder: (context, animation, secondaryAnimation) => child, // 转场动画构建 transitionsBuilder: (context, animation, secondaryAnimation, child) { // secondaryAnimation对应栈顶页面的退出动画 final exitAnimation = ReverseAnimation(secondaryAnimation).drive( CurveTween(curve: exitCurve), ); // animation对应新页面的进入动画 final enterAnimation = animation.drive( CurveTween(curve: enterCurve), ); // 这里用缩放动画示例,你可以替换成滑动、渐变等任意动画 return ScaleTransition( scale: exitAnimation, // 旧页面退出时用bounceOut缩放 child: ScaleTransition( scale: enterAnimation, // 新页面进入时线性缩放 child: child, ), ); }, ); } }
2. 在Navigation 2.0中使用
把原来的MaterialPage替换成这个自定义Page即可:
Navigator( pages: [ CustomTransitionPage(child: HomeScreen()), // 当需要跳转到详情页时,添加这个页面 if (isShowingDetails) CustomTransitionPage(child: DetailsScreen()), ], onPopPage: (route, result) { if (!route.didPop(result)) return false; // 这里处理你的栈状态更新逻辑,比如将isShowingDetails设为false return true; }, )
方案二:自定义TransitionDelegate(全局统一控制转场)
如果想让整个应用的页面退出都使用bounceOut动画,或者根据不同Page类型设置差异化转场,自定义TransitionDelegate会更灵活。
1. 定义自定义TransitionDelegate
class BounceOutTransitionDelegate extends TransitionDelegate<void> { @override Iterable<RouteTransitionRecord> resolve({ required List<RouteTransitionRecord> newPageRouteHistory, required Map<RouteTransitionRecord, RouteTransitionRecord> locationToExitingPageRoute, required Map<RouteTransitionRecord, List<RouteTransitionRecord>> pageRouteToPagelessRoutes, }) { final List<RouteTransitionRecord> results = []; // 处理所有退出的页面,给它们添加bounceOut动画 for (final exitingRecord in locationToExitingPageRoute.values) { if (exitingRecord.isWaitingForEnteringDecision) { exitingRecord.markForPop(exitingRecord.route.currentResult); // 设置退出动画曲线为bounceOut exitingRecord.pushReplacementTransition = CurvedAnimation(parent: exitingRecord.animation!, curve: Curves.bounceOut); } results.add(exitingRecord); } // 处理所有进入的页面,使用默认进入动画 for (final pageRoute in newPageRouteHistory) { if (pageRoute.isWaitingForExitingDecision) { pageRoute.markForAdd(); } results.add(pageRoute); } return results; } }
2. 在Navigator中启用这个Delegate
Navigator( transitionDelegate: BounceOutTransitionDelegate(), pages: [ MaterialPage(child: HomeScreen()), if (isShowingDetails) MaterialPage(child: DetailsScreen()), ], onPopPage: (route, result) { if (!route.didPop(result)) return false; // 更新栈状态逻辑 return true; }, )
几个关键注意点
- 不管用哪种方案,
onPopPage方法必须正确处理栈状态更新,否则Navigation 2.0的页面栈会出现异常。 - 如果需要更复杂的动画组合(比如缩放+渐变),可以在
transitionsBuilder里嵌套多个动画组件,或者使用flutter_animate这类库简化代码。 - 测试时要确保页面栈的触发逻辑正确(比如
isShowingDetails的状态变化),否则可能看不到预期的转场效果。
内容的提问来源于stack exchange,提问作者Hazhar Aziz
相关产品推荐
相关产品推荐

