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

Flutter如何实现按钮双向滑动(已完成正向滑动)

实现双向滑动按钮的完整方案

要实现双向滑动(左右均可滑至触发状态),你需要调整动画范围、拖拽逻辑和状态判断,以下是具体修改步骤和完整代码:

核心修改点

  • 扩展动画控制器范围:把控制器取值范围从默认的0.0-1.0改成-1.0-1.0,左滑对应负数值,右滑对应正数值
  • 调整滑动动画的Tween:让动画从Offset(-1.0, 0.0)到Offset(1.0, 0.0),对应按钮从最左滑到最右
  • 修改拖拽处理逻辑:允许拖拽百分比范围扩展到-1.0-1.0,并在拖拽结束时根据滑动距离判断是回弹还是滑到端点
  • 增加反向滑动的状态处理:添加左滑完成后的图标显示逻辑,同时调整文本隐藏的触发条件

修改后的完整代码

class _SlideButtonState extends State<SlideButton> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<Offset> _slideAnimation;
  bool _showCheckIcon = false; // 右滑完成显示
  bool _showCloseIcon = false; // 左滑完成显示
  bool _hideSlideText = false;
  late Offset _dragStart;

  @override
  void initState() {
    super.initState();
    // 扩展控制器范围到-1.0到1.0,支持双向滑动
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 500),
      lowerBound: -1.0,
      upperBound: 1.0,
    );
    // 动画覆盖从最左到最右的范围,对应控制器value从-1到1
    _slideAnimation = Tween<Offset>(
      begin: const Offset(-1.0, 0.0),
      end: const Offset(1.0, 0.0),
    ).animate(_controller)
      ..addListener(() {
        setState(() {});
      })
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          // 右滑至端点触发完成状态
          setState(() {
            _showCheckIcon = true;
            _hideSlideText = true;
          });
        } else if (status == AnimationStatus.dismissed) {
          // 左滑至端点触发初始状态(对应控制器lowerBound)
          setState(() {
            _showCloseIcon = true;
            _hideSlideText = true;
          });
        }
      });
  }

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

  void _onHorizontalDragUpdate(DragUpdateDetails details) {
    final dx = details.globalPosition.dx - _dragStart.dx;
    double slidePercent = dx / context.size!.width;
    // 限制滑动百分比在-1.0到1.0之间,避免超出按钮范围
    slidePercent = slidePercent.clamp(-1.0, 1.0);
    _controller.value = slidePercent;
  }

  void _onHorizontalDragEnd(DragEndDetails details) {
    // 设置滑动阈值,超过按钮宽度一半则触发端点状态,否则回弹到中间
    const threshold = 0.5;
    if (_controller.value > threshold) {
      // 滑到最右,执行正向动画
      _controller.forward();
    } else if (_controller.value < -threshold) {
      // 滑到最左,执行反向动画
      _controller.reverse();
    } else {
      // 回弹到初始中间位置
      _controller.animateTo(0.0, duration: const Duration(milliseconds: 300));
    }
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onHorizontalDragDown: (details) {
        _dragStart = details.globalPosition;
        // 拖拽开始时重置状态,允许再次滑动
        if (_hideSlideText) {
          setState(() {
            _showCheckIcon = false;
            _showCloseIcon = false;
            _hideSlideText = false;
          });
        }
      },
      onHorizontalDragUpdate: _onHorizontalDragUpdate,
      onHorizontalDragEnd: _onHorizontalDragEnd,
      child: Container(
        height: 50.0,
        width: 250,
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.circular(25.0),
        ),
        child: Stack(
          alignment: Alignment.center,
          children: [
            if (!_hideSlideText)
              ClipRRect(
                borderRadius: BorderRadius.circular(25.0),
                child: SlideTransition(
                  position: _slideAnimation,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      const Icon(Icons.arrow_back_ios, color: Colors.white),
                      const SizedBox(width: 8),
                      const Text(
                        'slide to continue or cancel',
                        style: TextStyle(
                          color: Colors.white,
                          fontSize: 16.0,
                        ),
                      ),
                      const SizedBox(width: 8),
                      const Icon(Icons.arrow_forward_ios, color: Colors.white),
                    ],
                  ),
                ),
              ),
            // 右滑完成显示的对勾图标
            AnimatedOpacity(
              opacity: _showCheckIcon ? 1.0 : 0.0,
              duration: const Duration(milliseconds: 250),
              child: const Icon(
                Icons.check,
                color: Colors.black,
                size: 30.0,
              ),
            ),
            // 左滑完成显示的关闭图标
            AnimatedOpacity(
              opacity: _showCloseIcon ? 1.0 : 0.0,
              duration: const Duration(milliseconds: 250),
              child: const Icon(
                Icons.close,
                color: Colors.black,
                size: 30.0,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

关键细节说明

  1. 动画控制器范围:通过lowerBound: -1.0让控制器支持负数值,对应左滑的动画进度
  2. 拖拽结束逻辑:设置0.5的阈值,只有滑动超过按钮宽度的一半时才会触发端点状态,否则回弹到中间,提升交互体验
  3. 状态重置:在onHorizontalDragDown时重置图标显示状态,允许用户再次滑动
  4. UI调整:添加了左右箭头提示双向滑动,同时增加左滑完成的关闭图标,匹配你期望的效果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 13:07:09