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

Flutter自定义淡入淡出路由实现侧滑返回淡入淡出动画

问题描述

我实现了一个CustomPageRoute,push新页面时的淡入动画正常,但侧滑返回时始终无法使用淡入淡出动画,试过多种方案,最接近的效果还是会出现页面滑入/滑出。我需要push和pop(包括侧滑返回)都只使用淡入淡出动画。

我的Flutter淡入过渡代码如下:

class FadePageTransition extends Page {
  final Widget page;

  const FadePageTransition({
    required this.page,
    LocalKey? key,
    String? restorationId,
  }) : super(
          key: key,
          restorationId: restorationId,
        );

  @override
  Route createRoute(BuildContext context) {
    return FadeRoute(
      child: page,
      routeSettings: this,
    );
  }
}

class FadeRoute<T> extends PageRoute<T> {
  final Widget child;
  final RouteSettings routeSettings;

  FadeRoute({
    required this.child,
    required this.routeSettings,
  });

  @override
  RouteSettings get settings => routeSettings;

  @override
  Color? get barrierColor => Palette.black;

  @override
  String? get barrierLabel => null;

  @override
  Widget buildPage(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
  ) {
    return FadeTransition(
      opacity: animation,
      child: child,
    );
  }

  @override
  bool get maintainState => true;

  @override
  Duration get transitionDuration => const Duration(
        milliseconds: transitionDurationInMS,
      );
}

另外,iOS Swift中实现类似功能的参考代码:

// 处理下拉手势
@objc private func handlePan(gestureRecognizer:UIPanGestureRecognizer) {
    // 根据用户滑动距离计算进度
    let translation = panGR.translation(in: nil)
    let progress = translation.y / 2 / view.bounds.height
    
    switch panGR.state {
    case .began:
        // 正常开始过渡
        self.dismissView()
        break
    case .changed:
        
        Hero.shared.update(progress)
        
    default:
        // 根据进度和触摸速度决定完成或取消过渡
        if progress + panGR.velocity(in: nil).y / view.bounds.height > 0.3 {
            self.dismissView()
            Hero.shared.finish()
        } else {
            Hero.shared.cancel()
        }
    }
}
解决方案

要实现侧滑返回时也用淡入淡出动画,需要从两个核心点修改:

1. 重写PageRoute的动画控制方法

默认PageRoute的buildPage仅负责页面内容渲染,路由切换的动画逻辑由buildTransitions控制。之前的代码只在buildPage里加了淡入动画,没覆盖pop场景的逻辑,修改如下:

class FadeRoute<T> extends PageRoute<T> {
  final Widget child;
  final RouteSettings routeSettings;

  FadeRoute({
    required this.child,
    required this.routeSettings,
  });

  @override
  RouteSettings get settings => routeSettings;

  @override
  Color? get barrierColor => Palette.black;

  @override
  String? get barrierLabel => null;

  @override
  Widget buildPage(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
  ) {
    return child; // 页面内容直接返回,动画交给buildTransitions处理
  }

  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
    // animation在push时从0→1,pop时从1→0,直接关联淡入淡出
    return FadeTransition(
      opacity: animation,
      child: child,
    );
  }

  @override
  bool get maintainState => true;

  @override
  Duration get transitionDuration => const Duration(milliseconds: transitionDurationInMS);
}

2. 自定义侧滑手势关联动画进度

iOS默认侧滑手势会触发系统滑动动画,需要替换为自定义手势,让淡入动画跟随拖拽进度变化,逻辑和你提供的iOS代码一致:

class TargetPage extends StatefulWidget {
  const TargetPage({super.key});

  @override
  State<TargetPage> createState() => _TargetPageState();
}

class _TargetPageState extends State<TargetPage> with SingleTickerProviderStateMixin {
  late final AnimationController _animController;
  double _dragProgress = 0.0;

  @override
  void initState() {
    super.initState();
    _animController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: transitionDurationInMS),
      value: 1.0, // 初始为完全显示状态
    );
  }

  @override
  void dispose() {
    _animController.dispose();
    super.dispose();
  }

  void _onDragUpdate(DragUpdateDetails details) {
    // 根据水平拖拽距离计算进度(基于屏幕宽度)
    _dragProgress = details.globalPosition.dx / MediaQuery.of(context).size.width;
    _animController.value = 1 - _dragProgress;
  }

  void _onDragEnd(DragEndDetails details) {
    if (_dragProgress > 0.3) {
      // 拖拽进度达标,完成返回
      _animController.reverse().then((_) {
        Navigator.of(context).pop();
      });
    } else {
      // 进度不足,恢复页面显示
      _animController.forward();
    }
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onHorizontalDragUpdate: _onDragUpdate,
      onHorizontalDragEnd: _onDragEnd,
      child: FadeTransition(
        opacity: _animController,
        child: Scaffold(
          // 页面内容区域
          appBar: AppBar(title: const Text("目标页面")),
          body: const Center(child: Text("侧滑返回时会淡入淡出")),
        ),
      ),
    );
  }
}

关键说明

  • buildTransitions中的animation参数会自动适配push/pop场景,无需额外判断;
  • 自定义手势时,通过拖拽进度实时更新动画控制器的value,实现和iOS代码一致的“跟随手势渐变”效果;
  • 如果想保留系统侧滑手势的触发逻辑,也可以通过CupertinoPageRoute的transitionBuilder参数替换动画,但自定义PageRoute+手势的方案可控性更强。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 09:21:35