View事件分发及传递实践--自定义侧滑删除控件

本文详细介绍了自定义侧滑删除控件的实现过程,包括View的scrollBy()、scrollTo()方法,OverScroller类的使用,事件拦截与处理机制,以及如何在ListView或RecyclerView中应用。同时,解决了滑动与点击冲突、多Item滑动等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

侧滑删除控件常用于ListView或者RecyclerView中,侧滑删除当前条目;很早就有自定义侧滑删除控件了,只是之前直接拿来别人的直接来使用,没有太注意其中的具体的实现细节;通过深入理解View的事件传递及分发机制之后,自己动手加深相关理解,特意自己来实现相关的细节;

知识准备:

View的scrollBy()、scrollTo()方法:

scrollBy() 是让View相对于当前的位置滚动某段距离,而scrollTo() 则是让View相对于初始的位置滚动某段距离。不管是scrollTo()还是scrollBy()方法,滚动的都是该View内部的内容;滚动效果是跳跃式的,没有任何平滑滚动的效果。

OverScroller或者Scroller类:用来收集生成滚动动画所需的数据;例如,响应于拖动手势。用于改变View的滑动距离;computeScrollOffset()方法:返回true,表示动画还没有完成,可以获取最新的位置;false表示动画已经完成了。类似于属性动画,可以用来实现回弹,滑动等效果。

  1. 初始化 scroller = new OverScroller(getContext());
  2. 调用scroller.startScroll();
  3. 重写computeScroll()方法,调用scroller.computeScrollOffset()判断动画是否完成;
具体实现过程

继承LinearLayout类

public class SideSlipDeleteView extends LinearLayout {
    private void initView() {
        setOrientation(HORIZONTAL);
        scroller = new OverScroller(getContext());
        //系统规定的滑动的最小距离;
        touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    }
}

重写onInterceptTouchEvent()方法:在左滑和右滑的时候拦截事件;其他时候不拦截事件;

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {

    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastX = ev.getX();
            lastY = ev.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            //disX>0 右滑;disX<0 左滑;
            float disX = ev.getX() - lastX;
            float disY = ev.getY() - lastY;

            //判断是X轴滑动还是Y轴滑动;X轴滑动就拦截;
            //Logger.e("disX::" + disX + ",,disY::" + disY + ",,dix::" + (disX - disY) + ",,,getScrollX()::" + getScrollX());

            //左滑
            if (Math.abs(disX) - Math.abs(disY) > touchSlop && disX < 0 && getScrollX() < getRightWidth()) {
                return true;
            }
            //右滑
            if (Math.abs(disX) - Math.abs(disY) > touchSlop && disX > 0 && getScrollX() > 0) {
                //Logger.e("onInterceptTouchEvent  action::" + ev.getAction());
                return true;
            }
            break;
    }
    return super.onInterceptTouchEvent(ev);
}

onTouchEvent():如果事件传递到SideSlipDeleteView;onTouchEvent()返回true,表示消费该事件;否则的话ListView的onTouchEvent()方法会返回True,move和up就不会再往SideSlipDeleteView继续传递了。

但是SideSlipDeleteView的onTouchEvent()返回true,ListView的条目点击事件会被拦截掉;并且点击子View及自身的setClickListener()的onClick()也会被拦截掉,需要在UP中调用performClick()方法;

//onTouchEvent 返回true 表示消费该事件;
@Override
public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastX = ev.getX();
            lastY = ev.getY();

            lastDownX = lastX;
            if (listener != null) {
                listener.slipDeleteMove(this);
            }
            break;
        case MotionEvent.ACTION_MOVE:
            //disX>0 右滑;disX<0 左滑;
            float disX = ev.getX() - lastX;
            float disY = ev.getY() - lastY;
            lastX = ev.getX();
            lastY = ev.getY();

            //左滑
            if (Math.abs(disX) - Math.abs(disY) > touchSlop && disX < 0 && getScrollX() < getRightWidth()) {

                if (getScrollX() + (-disX) > getRightWidth()) {//临界值
                    //正值向左移动,负值向右移动
                    scrollBy(getRightWidth() - getScrollX(), 0);
                } else {
                    scrollBy(-(int) disX, 0);
                }
            }

            //右滑
            if (Math.abs(disX) - Math.abs(disY) > touchSlop && disX > 0 && getScrollX() > 0) {
                if (getScrollX() - disX < 0) {
                    scrollBy(getScrollX(), 0);
                } else {
                    scrollBy(-(int) disX, 0);
                }
            }
            break;

        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:

            float upX = ev.getX();
            //左滑
            if (upX - lastDownX < 0) {
                slipLeft();
            }
            //右滑
            if (upX - lastDownX > 0) {
                slipIn();
            }

            //避免滑动事件和点击事件冲突;
            if (Math.abs(upX - lastDownX) < touchSlop && Math.abs(upX - lastDownX) < touchSlop) {
                //执行setOnClickListener()方法;
                Logger.e("调用performClick()");
                performClick();
            }
            break;
    }
    return true;
}

自定义监听事件:确保只有一个Item

public interface OnSlipDeleteStateChangeListener {
    //用于完全显示删除
    void slipDeleteOpen(SideSlipDeleteView sideSlipDeleteView);
	//用于完全隐藏
    void slipDeleteClose(SideSlipDeleteView sideSlipDeleteView);
	//用在onTouchEvent的down中;
    void slipDeleteMove(SideSlipDeleteView sideSlipDeleteView);
}

OnSlipDeleteStateChangeListener listener;

public void setSlipDeleteStateChangeListener(OnSlipDeleteStateChangeListener listener) {
    this.listener = listener;
}

在Adapter中设置监听

viewHolder.sideSlipDeleteView.setSlipDeleteStateChangeListener(new SideSlipDeleteView.OnSlipDeleteStateChangeListener() {
            @Override
            public void slipDeleteOpen(SideSlipDeleteView sideSlipDeleteView) {
                mSideSlipDeleteView = sideSlipDeleteView;
            }

            @Override
            public void slipDeleteClose(SideSlipDeleteView sideSlipDeleteView) {
                if (mSideSlipDeleteView == sideSlipDeleteView) {
                    mSideSlipDeleteView = null;
                }
            }

            @Override
            public void slipDeleteMove(SideSlipDeleteView sideSlipDeleteView) {
                if (mSideSlipDeleteView != null && mSideSlipDeleteView != sideSlipDeleteView) {
                    mSideSlipDeleteView.closeSlipDelete();
                }
            }
        });
实现过程问题总结
  • 问题一:侧滑删除 放在ListView中,为什么只接受了Down,Move和UP 都没有接收到??

    因为AbsTouchEvent的onTouchEvent返回true,如果down事件不被SlipDeleteView消费,就会被AbsListView的OnTouchEvent()消费,Move,Up都会直接分发到AbsListView;不会再往传递了;所有SlipDeleteView的onTouchEvent()返回true;

  • 问题二:但是如果在SlipDeleteView的OnTouchEvent()直接返回true,ListView的条目点击事件就会被没有了;该如何解决???

    只能用SlipDeleteView的点击事件替换ListView的条目点击事件;

  • 问题三:SlipDeleteView的点击事件冲突??

    那就在Up中调用performClick(),会调用setOnClickListener()监听;

  • 问题四:如何只滑出一个Item??

    move 将上一个已经滑出的Item时,关闭;Open的时候,记录当前的SlipDeleteView;关闭的时候,将其制null;

自定义侧滑删除控件可以单独使用或者放在ListView,RecyclerView中使用;以上就是具体的实现过程,如有问题,请多指教,谢谢!!

源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值