Android 侧滑删除编辑

第一次写,写的不好或有错误的地方请多多包涵,闲话不多说,先上两张效果图。

Android 侧滑删除编辑_第1张图片
Paste_Image.png
Android 侧滑删除编辑_第2张图片
Paste_Image.png

实现此种效果,大致有两种方法,此效果主要是处理滑动事件冲突,第一是重写RecyclerView,重写onInterceptTouchEvent和onTouchEvent。第二种方法时重写RecyclerView的子View,主要也是重写这两个方法,如果是继承ViewGroup,那么要重写onMeasure和onLayout,如果是继承LinearLayout则只要重写事件分发的几个方法就好。这里采用的第二种方法,重写RecyclerView的子View。下面主要说说重写的核心方法:

先看onInterceptTouchEvent方法:

@Override
    public boolean onInterceptTouchEvent(MotionEvent e) {

        boolean consume = false;
        int x = (int) e.getX();
        int y = (int) e.getY();
        switch (e.getAction()){

            case MotionEvent.ACTION_DOWN:
                //获取手指按下时的坐标
                mDownX = (int) e.getX();
                mDownY = (int) e.getY();
                Log.d(TAG, "mDownX:"+ mDownX);
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "onInterceptTouchEvent:ACTION_MOVE");
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;

                if(Math.abs(deltaX) > mTouchSlop || Math.abs(deltaY) > mTouchSlop){
                    //当y方向滑动距离小于x方向时,拦截事件,交给自己处理
                    if(Math.abs(deltaX) > Math.abs(deltaY)){
                        Log.d(TAG, "onInterceptTouchEvent");
                        //不允许父元素拦截事件
                        getParent().requestDisallowInterceptTouchEvent(true);
                        //此时拦截事件,直接调用onTouchEvent,传入onTouchEvent的ACTION_MOVE事件中,不再走onTouchEvent的ACTION_DOWN事件
                        consume = true;
                    }else {
                        getParent().requestDisallowInterceptTouchEvent(false);
                    }
                }

                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "ACTION_UP");
                break;
        }
        mLastX = x;
        mLastY = y;
        return consume;
    }

RecyclerView继承的是ViewGroup,因此默认不拦截任何点击事件,

//当我们重写子View时,必须在合适的时机调用此方法,此处是不允许父元素也就是RecyclerView拦截该事件,此方法时设置一个标记位,具体的可以看源码或者具体了解一下事件分发机制。
getParent().requestDisallowInterceptTouchEvent(true);

onInterceptTouchEvent主要的逻辑是拦截事件,具体一点,当我们在x轴滑动的距离大于y轴滑动的距离时,我们需要子View拦截事件,也就是不允许父元素拦截事件,即

//不允许父元素拦截事件
getParent().requestDisallowInterceptTouchEvent(true);
//此时拦截事件,直接调用onTouchEvent,传入onTouchEvent的ACTION_MOVE事件中,不再走onTouchEvent的ACTION_DOWN事件
consume = true;

当我们完成具体的拦截逻辑后,具体的执行逻辑在onTouchEvent中执行,下面看看代码:

@Override
    public boolean onTouchEvent(MotionEvent e) {
        switch (e.getAction()){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                mMoveX = (int) e.getX();
                mMoveY = (int) e.getY();
                Log.d(TAG, "mMoveX - mDownX:"+ (mMoveX - mDownX));

                scrollBy(-(mMoveX - mDownX),0);

                int scrollX = getScrollX();
                if (scrollX > mItemLength) {
                    scrollTo(mItemLength, 0);
                    isOpen = true;
                }
                if (scrollX < 0) {
                    scrollTo(0, 0);
                    isOpen = false;
                }

                break;
            case MotionEvent.ACTION_UP:
                //滑动超过一定距离时,自动关闭或开启menu
                int upX = (int) getX();
                if(Math.abs(getScrollX())>= mMenuWidth && getScrollX() > 0 || Math.abs(getScrollX())< mMenuWidth && getScrollX() < 0){
                    open();
                }else if(Math.abs(getScrollX())>= mMenuWidth && getScrollX() < 0 || Math.abs(getScrollX())< mMenuWidth && getScrollX() > 0){
                    close();
                }
                break;
        }
        mDownX = mMoveX;
        mDownY = mMoveY;
        return true;
    }

具体的执行落在onTouchEvent的ACTION_MOVE中执行,这里的代码也不复杂,最后在ACTION_UP时判断,当滑动超过一定距离时,松开手指可通过Scroller实现平缓滑动的效果。

最后为了实现更好的效果,重写了RecyclerView的onInterceptTouchEvent方法:

 @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {

        switch (e.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.d(TAG, "onInterceptTouchEvent: down");
                int childCount = getChildCount();
                for(int i=0;i

具体的效果是,当手指触发ACTION_DOWN事件时,如果有未关闭的子View,那么关闭它。

最后一点要说明的是,在RecyclerView的Adapter中,对于Menu的点击事件采用的是onTouchListener:

((ViewHolder)holder).tvEdit.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if(event.getAction() == MotionEvent.ACTION_DOWN){
                    int position = holder.getAdapterPosition();
                    Log.d(TAG, "onTouch: edit:"+position);
                    if(onClickListenerEditOrDelete != null){
                        onClickListenerEditOrDelete.OnClickListenerEdit(position);
                    }
                    return true;
                }
                return true;
            }
        });

因为OnTouchListener的优先级高于OnClickListener,当使用OnClickListener时,有时能触发点击事件有时不能,如有同学知道所以然还想好好请教一番。

具体的实现逻辑就是这样,重点是处理各种事件的滑动冲突,带一点自定义View的基本使用,Scroller和View的滑动机制。demo还有许多不足的地方,还需要多多优化。

具体代码下载

你可能感兴趣的:(Android 侧滑删除编辑)