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

Flutter实现旧屏退出新屏进入转场动画的路由实践问题

Flutter序列路由转场问题解答

1. 这类序列转场动画的最佳实践

你之前的实现走了弯路:完全不需要手动获取旧页面实例,Flutter路由转场API天生支持新旧页面独立动画控制,之前的问题是没有正确使用框架提供的secondaryAnimation参数,才会需要手动查找旧页面。
框架转场逻辑本身就会在转场期间同时渲染新旧两个路由:

  • 新路由的animation参数:控制新页面从0(初始位置/不可见)到1(最终位置/完全可见)的进入过程
  • 旧路由的secondaryAnimation参数:控制旧页面从0(初始位置/完全可见)到1(退出位置/不可见)的退出过程
    你需要的「旧页先完全退出、新页再进入」的序列效果,只需要给两个动画加对应时间区间Interval即可,根本不需要手动在Stack里塞入旧页面。
    推荐直接把转场逻辑封装为全局PageTransitionsBuilder,所有路由自动生效,不需要单独在每个页面处理,代码示例:
class SequenceSlideTransitionBuilder extends PageTransitionsBuilder {
  const SequenceSlideTransitionBuilder();
  @override
  Widget buildTransitions<T>(
    PageRoute<T> route,
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
    Widget child,
  ) {
    // 旧路由响应secondaryAnimation,前50%时长完成下滑退出
    if (!route.isCurrent || animation.status == AnimationStatus.reverse) {
      final exitAnim = Tween<Offset>(begin: Offset.zero, end: const Offset(0, 1))
          .chain(CurveTween(curve: const Interval(0, 0.5, curve: Curves.linear)))
          .animate(secondaryAnimation);
      return SlideTransition(position: exitAnim, child: child);
    }
    // 新路由响应animation,后50%时长完成上滑进入
    final enterAnim = Tween<Offset>(begin: const Offset(0, 1), end: Offset.zero)
        .chain(CurveTween(curve: const Interval(0.5, 1, curve: Curves.linear)))
        .animate(animation);
    return SlideTransition(position: enterAnim, child: child);
  }
}

全局配置方式,直接挂在MaterialApp主题下即可:

MaterialApp(
  theme: ThemeData(
    pageTransitionsTheme: const PageTransitionsTheme(
      builders: {
        TargetPlatform.android: SequenceSlideTransitionBuilder(),
        TargetPlatform.iOS: SequenceSlideTransitionBuilder(),
      },
    ),
  ),
  // 正常配置onGenerateRoute即可,不需要额外处理转场
  onGenerateRoute: MyCustomRouting.generateRoute,
)

2. generateRoute方法内获取当前页面Widget的方案

不需要全静态实现也能拿到,两种常用方案:

  • 提前给MaterialApp配置navigatorKey: GlobalKey<NavigatorState>(),onGenerateRoute触发时,通过navigatorKey.currentState可以拿到当前路由栈,遍历栈内Route找到isCurrent == true的栈顶路由即可。如果是自定义Route子类,可以给Route加一个属性持有对应的页面Widget实例,就能直接取到。
  • 不自定义Route属性的话,可以通过ModalRoute.of(navigatorKey.currentContext!)拿到当前的ModalRoute,顺着它的subtreeContext向下查找对应页面的Element,也能拿到Widget实例,但这种方式偏hack,非必要不推荐。
    注意:按照最佳实践实现转场根本不需要获取当前页面,这个方案属于绕路,没有实际必要。

3. 组件树任意位置获取栈顶页面Widget的方法

有两种稳定方案:

  • 复用上面提到的全局navigatorKey,任意位置都能通过navigatorKey.currentState拿到路由栈,筛选出栈顶活跃Route,只要构建路由时统一给Route加了持有页面Widget的属性,就能直接读取。
  • 注册全局RouteObserver<PageRoute>,挂在MaterialApp的navigatorObservers属性上,监听所有路由的push/pop/replace事件,每次栈顶路由变化时,把当前栈顶的页面Widget实例存到InheritedWidget或者服务定位器中,组件树任意位置都能直接读取。
    注意:绝大多数业务场景不需要拿到栈顶页面的Widget实例,拿路由名、路由参数就足够完成业务逻辑,直接持有Widget实例很容易引发生命周期不匹配的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 07:48:20