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

Flutter自定义拖动容器回弹Animation动画仅单次生效问题求解

问题原因
  • AnimationController首次调用forward()执行完动画后会进入completed完成状态,没有重置的情况下后续再次调用forward()不会触发动画执行,是第二次拖动后无回弹的核心原因。
  • 原代码把动画实例创建、监听添加的逻辑放到了onVerticalDragUpdate回调中,拖动过程中每触发一次偏移更新就会重复创建动画、重复添加监听,会导致监听累积触发,引发状态异常和性能损耗。
修复方案
  1. 拖动更新回调中仅保留高度实时计算修改的逻辑,删除动画相关代码
  2. 拖动结束回调中先重置动画控制器,再创建对应回弹动画执行
  3. 单独抽离动画更新高度的监听方法,避免重复添加监听
完整修改后代码
class _HomeViewState extends State<HomeView> with TickerProviderStateMixin {
  HomeViewController homeController = HomeViewController();

  late Animation<double> translateBetween;
  late AnimationController customAnimationController;

  @override
  void initState() {
    customAnimationController = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 2000));
    super.initState();
  }

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

  // 单独抽离动画更新高度的逻辑
  void _updateAnimatedHeight() {
    homeController.animatedHeight = translateBetween.value;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          Obx(() {
            return Container(
              alignment: Alignment.topCenter,
              child: Column(
                children: [
                  GestureDetector(
                    onVerticalDragUpdate: (dragUpdateDetails) {
                      // 仅保留拖动时实时计算高度的逻辑
                      double newHeight = homeController.animatedHeight -
                          dragUpdateDetails.primaryDelta! / 6.5;
                      // 用clamp做边界限制更简洁
                      homeController.animatedHeight = newHeight.clamp(20.0, 95.0);
                    },
                    onVerticalDragEnd: (dragEndDetails) {
                      // 先重置动画控制器,清除上一次的完成状态
                      customAnimationController.reset();
                      // 释放时才创建回弹动画,begin为当前高度,end为初始高度20
                      final tween = Tween<double>(
                          begin: homeController.animatedHeight, end: 20.0);
                      translateBetween = tween.animate(CurvedAnimation(
                        parent: customAnimationController,
                        curve: Curves.bounceOut,
                      ));
                      // 先移除旧监听避免重复触发
                      translateBetween.removeListener(_updateAnimatedHeight);
                      translateBetween.addListener(_updateAnimatedHeight);
                      // 执行回弹动画
                      customAnimationController.forward();
                    },
                    child: Column(
                      children: [
                        const Icon(Icons.arrow_upward_rounded),
                        SizedBox(
                          width: 20.w,
                          child: Divider(
                            color: Colors.black,
                            thickness: 0.3.w,
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
              width: 100.w,
              height: homeController.animatedHeight.h,
              decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.only(
                      topLeft: Radius.circular(5.w),
                      topRight: Radius.circular(5.w))),
            );
          })
        ],
      ),
    );
  }
}

控制器代码无需修改:

class HomeViewController extends GetxController {
  final RxDouble _animatedHeight = 20.0.obs;
  double get animatedHeight => _animatedHeight.value;
  set animatedHeight(double value) => _animatedHeight.value = value;
}

内容的提问来源于stack exchange,提问作者Mehmet Karanlık

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 08:54:07