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

Flutter对角线帘式页面过渡:如何提前显示新页面?

解决Flutter对角窗帘过渡时显示新页面的问题

你的问题核心在于:之前的实现应该是先在当前页面执行窗帘动画,等动画结束后才跳转新页面,这就导致动画过程中新页面还没被加载,所以下面是空白。要让新页面在动画一开始就显示,我们需要把两个页面的过渡逻辑整合到自定义PageRoute中,让新页面作为底层,旧页面拆分成窗帘层在上面做拉开动画。

具体实现步骤

1. 自定义窗帘裁剪器

我们需要两个CustomClipper来分别裁剪旧页面的左上和右下部分,配合动画值实现窗帘拉开的效果:

class _LeftCurtainClipper extends CustomClipper<Path> {
  final double progress;

  _LeftCurtainClipper(this.progress);

  @override
  Path getClip(Size size) {
    final path = Path();
    // 随着progress从0到1,裁剪区域向左上角收缩
    final x = size.width * progress;
    final y = size.height * progress;
    path.moveTo(x, y);
    path.lineTo(size.width, 0);
    path.lineTo(size.width, size.height);
    path.lineTo(0, size.height);
    path.lineTo(x, y);
    return path;
  }

  @override
  bool shouldReclip(_LeftCurtainClipper oldClipper) {
    return oldClipper.progress != progress;
  }
}

class _RightCurtainClipper extends CustomClipper<Path> {
  final double progress;

  _RightCurtainClipper(this.progress);

  @override
  Path getClip(Size size) {
    final path = Path();
    // 随着progress从0到1,裁剪区域向右下角收缩
    final x = size.width * (1 - progress);
    final y = size.height * (1 - progress);
    path.moveTo(x, y);
    path.lineTo(0, 0);
    path.lineTo(0, size.height);
    path.lineTo(size.width, size.height);
    path.lineTo(x, y);
    return path;
  }

  @override
  bool shouldReclip(_RightCurtainClipper oldClipper) {
    return oldClipper.progress != progress;
  }
}

2. 自定义对角窗帘过渡路由

创建一个继承自PageRouteBuilder的自定义路由,在过渡逻辑中同时渲染新页面(底层)和旧页面的窗帘部分(上层):

class DiagonalCurtainPageRoute extends PageRouteBuilder {
  final Widget destination;

  DiagonalCurtainPageRoute({required this.destination})
      : super(
          transitionDuration: const Duration(milliseconds: 800),
          pageBuilder: (context, animation, secondaryAnimation) => destination,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            // 获取旧页面的实例
            final oldPage = ModalRoute.of(context)?.previousContentWidget;
            if (oldPage == null) return child;

            // 定义窗帘动画的进度值(从0到1)
            final curtainAnimation = Tween<double>(begin: 0, end: 1).animate(
              CurvedAnimation(parent: animation, curve: Curves.easeInOut),
            );

            return Stack(
              children: [
                // 新页面始终显示在底层
                child,
                // 旧页面的两个窗帘部分,随动画拉开
                AnimatedBuilder(
                  animation: curtainAnimation,
                  builder: (context, _) {
                    return Stack(
                      children: [
                        ClipPath(
                          clipper: _LeftCurtainClipper(curtainAnimation.value),
                          child: oldPage,
                        ),
                        ClipPath(
                          clipper: _RightCurtainClipper(curtainAnimation.value),
                          child: oldPage,
                        ),
                      ],
                    );
                  },
                ),
              ],
            );
          },
        );
}

3. 使用自定义路由跳转

在需要跳转的地方,用这个自定义路由替代默认的MaterialPageRoute:

// 示例:在首页按钮点击时跳转
ElevatedButton(
  onPressed: () {
    Navigator.push(
      context,
      DiagonalCurtainPageRoute(destination: const NewPage()),
    );
  },
  child: const Text('打开新页面'),
)

关键原理说明

  • 同时渲染两个页面:自定义路由中用Stack布局让新页面始终在底层,旧页面的裁剪部分在上方,动画过程中两者同时存在。
  • 裁剪动画:通过ClipPath和动画进度值动态修改旧页面的显示区域,模拟窗帘向对角拉开的效果。
  • 避免延迟跳转:不需要等动画结束再跳转,过渡逻辑完全由路由管理,动画开始时新页面就已渲染完成。

这样实现后,点击跳转按钮时,新页面会立刻显示在旧页面的窗帘下方,随着动画推进,窗帘逐渐拉开,全程不会出现空白区域,完美实现你想要的效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:11:17