如何在Flutter中实现指定效果的页面跳转过渡动画?
问题
我尝试使用Hero widget和页面路由实现类似这张gif的页面切换效果。
我也尝试过自定义页面路由,使用Stack widget包裹两个页面,并在buildTransitions中实现动画,但缩放对齐参数计算难度较高,且Hero widget不会从上一个页面中移除,实现代码如下:
class CustomPageRoute extends MaterialPageRoute { final Widget parentWidget; final Alignment parentAlignment; final Tween<double> childScaleTween; final double childWidth; CustomPageRoute( {required this.parentWidget, required this.parentAlignment, required this.childScaleTween, required this.childWidth, required WidgetBuilder builder, RouteSettings? settings}) : super(builder: builder, settings: settings); @override Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { return builder(context); } @override Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { Curve animatedCurves = Curves.linear; var scale = MediaQuery.of(context).size.width / childWidth; var scaleAnim = Tween(begin: 1.0, end: scale) .animate(CurvedAnimation(parent: animation, curve: animatedCurves)); var fadeAnim = Tween(begin: 1.0, end: 0.0) .animate(CurvedAnimation(parent: animation, curve: animatedCurves)); var childScaleAnim = childScaleTween .animate(CurvedAnimation(parent: animation, curve: animatedCurves)); var childFadeAnim = Tween(begin: 0.0, end: 1.0) .animate(CurvedAnimation(parent: animation, curve: animatedCurves)); return Stack(children: [ Container( color: Colors.white, ), ScaleTransition( scale: scaleAnim, alignment: parentAlignment, child: FadeTransition(opacity: fadeAnim, child: parentWidget)), ScaleTransition( scale: childScaleAnim, alignment: parentAlignment, child: FadeTransition(opacity: childFadeAnim, child: child)), ]); } }
请问还有其他可参考的实现思路吗?
解答
思路1:系统原生Hero配合PageRouteBuilder实现
你遇到的Hero不消失的问题,本质是自定义路由时没有把Hero的过渡逻辑纳入路由动画的生命周期。系统Hero本身已经处理了共享元素的挂载/卸载逻辑,不需要手动维护两个页面的Stack嵌套。只需要给两个页面的共享元素设置同一个tag,再用PageRouteBuilder自定义页面整体的过渡动画,Hero会自动完成共享元素的位移、缩放过渡,不需要手动计算对齐参数。
参考路由配置:
Navigator.of(context).push( PageRouteBuilder( transitionDuration: const Duration(milliseconds: 300), pageBuilder: (context, animation, secondaryAnimation) => const TargetPage(), transitionsBuilder: (context, animation, secondaryAnimation, child) { // 仅需处理页面背景、非共享元素的位移/透明度动画,Hero动画由系统自动处理 var fade = Tween<double>(begin: 0, end: 1).animate(animation); return FadeTransition(opacity: fade, child: child); }, ), );
思路2:使用官方animations库的OpenContainer组件
这是官方维护的动画组件,专门封装了容器展开、收起的过渡效果,内部已经处理了共享元素的对齐、缩放、页面生命周期逻辑,不需要手动计算参数。只需要把触发跳转的组件作为closedBuilder的返回值,目标页面作为openBuilder的返回值即可,直接实现你需要的过渡效果。
基础使用示例:
OpenContainer( transitionDuration: const Duration(milliseconds: 300), closedBuilder: (context, openContainer) => GestureDetector( onTap: openContainer, child: // 触发跳转的列表卡片组件 ), openBuilder: (context, closeContainer) => // 目标详情页面 )
思路3:自定义路由前先获取共享元素的位置参数
如果坚持要完全自定义路由动画,可以在点击跳转前,通过RenderBox拿到点击元素在屏幕中的全局坐标和尺寸,将这两个参数传给自定义路由,代替手动传入的parentAlignment和childWidth,避免手动对齐计算出错。
参考获取坐标代码:
// 点击元素时获取位置与尺寸 final renderBox = context.findRenderObject() as RenderBox; final offset = renderBox.localToGlobal(Offset.zero); final size = renderBox.size; // 把offset和size传给自定义路由,后续换算对齐参数直接使用这两个值即可
内容的提问来源于stack exchange,提问作者user13055561
相关产品推荐
相关产品推荐

