Flutter 滑动删除实现

转载请注明出处:王亟亟的大牛之路

前言

有一段时间没写文章了,第一是工作比较忙,第二是私事也比较多毕竟再过2年就而立之年了,然后去年主要是做RN+Android +技术调研。今年开始重心放在Flutter多端实现上,希望能做好吧。


效果

Flutter 滑动删除实现_第1张图片
一个可以做滑动删除等常用交互的一个控件(有时候写写改源码 有些时候读别人实现 有时候自己搭轮子 一直在做 总希望大家在需要的时候能有用吧)


项目结构

Flutter 滑动删除实现_第2张图片
一个比较纯粹的项目,没广告,也没乱七八糟的依赖,不会对使用者的依赖链路造成困扰。


如何使用

 LeftScroll(
              buttonWidth: 80,
              child: Container(
                height: 60,
                color: Colors.white,
                alignment: Alignment.center,
                child: Text(' Try Scroll Left'),
              ),
              buttons: <Widget>[
                LeftScrollItem(
                  text: 'delete',
                  color: Colors.red,
                  onTap: () {
                    print('delete');
                  },
                ),
                LeftScrollItem(
                  text: 'Edit',
                  color: Colors.orange,
                  onTap: () {
                    print('edit');
                  },
                ),
              ],
              onTap: () {
                print('tap row');
              },
            )

使用起来也很简单 一个buttons属性关联着几个LeftScrollItem,这个自定义控件可以实现我们滑动后出发的逻辑

疑问1:普通按钮行不行?
疑问2: 层级是如何实现的?


LeftScrollListItem

class LeftScrollListItem {
  final String key;
  final Widget child;
  final List<Widget> buttons;
  final Function onTap;
  LeftScrollListItem({
    @required this.key,
    @required this.child,
    @required this.buttons,
    @required this.onTap,
  });
}

LeftScrollListItem 的定义其实有点出乎意料,他不是一个Widget对象,也没做啥特殊的处理,更像是结构化子控件的工具。


LeftScroll

200多行 我们一段段来分析他

构造函数必传子控件,处理按钮,和一堆回调,比较重要和需要注意的是closeOnPop这个属性,这是失去焦点或者其他交互关闭滑动的一个判断flag,1.20后才支持。

  LeftScroll({
    this.key,
    @required this.child,
    @required this.buttons,
    this.onSlideStarted,
    this.onSlideCompleted,
    this.onSlideCanceled,
    this.onTap,
    this.buttonWidth: 80.0,
    this.onScroll,
    this.closeOnPop: true,
  }) : super(key: key);

初始化

 @override
  void initState() {
    super.initState();
    maxDragDistance = widget.buttonWidth * widget.buttons.length;//根据宽度计算滑动距离
    //处理水平拖拽事件
    gestures[HorizontalDragGestureRecognizer] =
        GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
      () => HorizontalDragGestureRecognizer(debugOwner: this),
      (HorizontalDragGestureRecognizer instance) {
        instance
          ..onDown = onHorizontalDragDown //开始拖拽触发
          ..onUpdate = onHorizontalDragUpdate//更新进度
          ..onEnd = onHorizontalDragEnd; //拖住结束触发
      },
    );

	//处理点击事件
    gestures[TapGestureRecognizer] =
        GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
      () => TapGestureRecognizer(debugOwner: this),
      (TapGestureRecognizer instance) {
        instance..onTap = widget.onTap;//回传给外部传入的点击事件
      },
    );

	//根据动画进度刷新状态
    animationController = AnimationController(
        lowerBound: -maxDragDistance,
        upperBound: 0,
        vsync: this,
        duration: Duration(milliseconds: 300))
      ..addListener(() {
        translateX = animationController.value;
        setState(() {});
      });
  }

 @override
 Widget build(BuildContext context) {
	......
	 return widget.closeOnPop
        ? WillPopScope(
            child: body,
            onWillPop: () async {
              if (translateX != 0) {
                close();
                return false;
              }
              return true;
            })
        : body;
 }

挂载的是一个接受异步方法的onWillPop当动画结束后,才会返回ture,使逻辑有效


几个逻辑方法

  void onHorizontalDragDown(DragDownDetails details) {
    //水平滑动开始回调外部传入的方法
    if (widget.onSlideStarted != null) widget.onSlideStarted.call();
  }

  void onHorizontalDragUpdate(DragUpdateDetails details) {
    translateX = (translateX + details.delta.dx).clamp(-maxDragDistance, 0.0);
    if (widget.onScroll != null) {
      //回传进度
      widget.onScroll.call(
        translateX / maxDragDistance * -1,
      );
    }
    setState(() {});
  }

void onHorizontalDragEnd(DragEndDetails details) {
    animationController.value = translateX;
    //收回来
    if (details.velocity.pixelsPerSecond.dx > 200) {
      close();
    } else if (details.velocity.pixelsPerSecond.dx < -200) {
    //滑动距离超过200(拉过阈值),张开
      open();
    } else {
       //超过一半就开
      if (translateX.abs() > maxDragDistance / 2) {
        open();
      } else {
        close();
      }
    }
  }

//张开按钮宽度
void open() {
    if (translateX != -maxDragDistance)
      animationController.animateTo(-maxDragDistance).then((_) {
        if (widget.onSlideCompleted != null) widget.onSlideCompleted.call();
      });
  }

  //闭合到0
  void close() {
    if (translateX != 0)
      animationController.animateTo(0).then((_) {
        if (widget.onSlideCanceled != null) widget.onSlideCanceled.call();
      });
  }

总结-实现过程

  1. 构建一个子控件对象规范传入的子控件和业务回调
  2. 构建一个通过手势处理+AnimationController 实现的一个拉开,关闭的动画过程
  3. 通过Stack+RawGestureDetector+WillPopScope的组合实现 布局+动画效果+异步关闭的最终效果

源码地址:https://github.com/mjl0602/left-scroll-actions

你可能感兴趣的:(Flutter)