实现RecyclerView侧滑菜单

RecyclerView默认可以使用ItemTouchHelper实现各种动作,但是如果需要实现类似qq的侧滑显示菜单的效果则需要自定义实现,主要实现方式就是自定义RecyclerView,重写dispatchTouchEvent,onTouchEvent事件。

实现步骤,

1.在dispatchTouchEvent中,

  • ACTION_DOWN事件:(1)如果点击的是已经打开的item则关闭并停止此次事件;(2)如果是点击的是item,则获取item的侧滑菜单的宽度。
  • ACTION_MOVE事件:左右滑动距离是否大于上下滑动距离且大于最小滑动距离,是的话则可以进行侧滑并设置一个标识(用于后面的MOVE事件是否进行判断),否则此次事件不进行侧滑,且尝试隐藏已经显示的菜单;
  • ACTION_UP事件:重置状态;

2.onTouchEvent中,

  • ACTION_MOVE事件:判断滑动的方向,并进行item的滑动,滑动到最小或最大距离时不再滑动。
  • ACTION_UP事件:判断当前滑动的距离,大于等于1/2就显示,否则就隐藏,使用Scroller来实现。

处理多点触控,使用mActivePointerId保存按压的id,以最后一个按压点为准。

dispatchTouchEvent和onTouchEvent的关键代码有这些

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (!canSide) {
            return super.dispatchTouchEvent(event);
        }
        int action = event.getActionMasked();
        if (isCancelTouch) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    isCancelTouch = false;
                    break;
            }
            return true;
        }
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mActivePointerId = event.getPointerId(0);
                mDownX = event.getX();
                mDownY = event.getY();

                //侧滑
                mPointPosition = pointToPosition(mDownX, event.getY());
                //LogUtil.i("*******pointToPosition(mDownX, mDownY): " + mPointPosition);
                if (mPointPosition != NO_POSITION) {
                    if (isSideShow && mPointChild != null) {
                        // 如果已经显示则直接关闭
                        View view = layoutManager.findViewByPosition(mPointPosition);
                        if (!mPointChild.equals(view)) {
                            turnNormal();
                            isCancelTouch = true;
                            return true;
                        }
                    }
                    if (positionIsItem()) {
                        //获取当前的item
                        View view = layoutManager.findViewByPosition(mPointPosition);
                        if (view instanceof ViewGroup) {
                            mPointChild = (ViewGroup) view;
                            getSideWidth();
                        } else {
                            return super.dispatchTouchEvent(event);
                        }
                    }
                    onLocked();
                } else {
                    if (isSideShow) {
                        turnNormal();
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (positionIsItem()) {
                    if (!isLockStatus) {
                        final int activePointerIndex = event.findPointerIndex(mActivePointerId);
                        final float x = event.getX(activePointerIndex);
                        final float y = event.getY(activePointerIndex);

                        float xGap = Math.abs(x - mDownX);
                        float yGap = Math.abs(y - mDownY);
                        // 判断是左右还是上下
                        if (xGap > yGap && xGap > mTouchSlop && positionIsItem()) {
                            isSide = true;
                            isLockStatus = true;
                        } else if (yGap > xGap && yGap > mTouchSlop) {
                            isSide = false;
                            isLockStatus = true;
                            if (isSideShow) {
                                // 上下滑动时隐藏右侧
                                turnNormal();
                            }
                        }
                        if (xGap > yGap && xGap > mTouchSlop && !isLockStatus) {
                            // 非锁定时
                            onUnLocked();
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                isLockStatus = false;
                onUnLocked();
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                final int index = event.getActionIndex();
                mDownX = event.getX(index);
                mActivePointerId = event.getPointerId(index);
                break;
            case MotionEvent.ACTION_POINTER_UP:
                final int pointerIndex = event.getActionIndex();
                final int pointerId = event.getPointerId(pointerIndex);
                if (pointerId == mActivePointerId) {
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                    mActivePointerId = event.getPointerId(newPointerIndex);
                }
                mDownX = event.getX(event.findPointerIndex(mActivePointerId));
                break;
            default:
                isLockStatus = false;
                onUnLocked();
                isSide = false;
                break;
        }
        return super.dispatchTouchEvent(event);
    }

   /**
     * 获取侧滑菜单的宽度
     */
    private void getSideWidth() {
        mSideWidth = 0;
        View sideView;
        int sideWidth;
        for (int i = 1, count = mPointChild.getChildCount(); i < count; i++) {
            sideView = mPointChild.getChildAt(i);
            if (sideView != null && (sideView.getVisibility() == VISIBLE)) {
                sideWidth = sideView.getLayoutParams().width;
                if (sideWidth <= 0) {
                    sideWidth = sideView.getWidth();
                }
                if (sideWidth <= 0) {
                    sideView.measure(0, 0);
                    sideWidth = sideView.getMeasuredWidth();
                }
                mSideWidth += sideWidth;
            }
        }
    }

    /**
     * 点击位置转为子view的position
     * @param x x
     * @param y y
     * @return position
     */
    private int pointToPosition(float x, float y) {
        View targetView = findChildViewUnder(x, y);
        if (targetView == null) return NO_POSITION;
        return getChildAdapterPosition(targetView);
    }

    /**
     * 判断点击的position是否item
     */
    private boolean positionIsItem() {
        if (getAdapter() == null) return false;
        int realPosition = mPointPosition;
        int itemCount = getAdapter().getItemCount();
        return realPosition >= 0 && realPosition < itemCount;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!canSide) {
            return super.onTouchEvent(event);
        }
        if (isCancelTouch) {
            return true;
        }
        if (isSide && positionIsItem()) {
            //事件响应
            requestDisallowInterceptTouchEvent(true);
            int action = event.getActionMasked();
            switch (action) {
                case MotionEvent.ACTION_POINTER_DOWN:
                case MotionEvent.ACTION_POINTER_UP:
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_MOVE:
                    MotionEvent cancelEvent = MotionEvent.obtain(event);
                    cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
                            (event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
                    onTouchEvent(cancelEvent);
                    final int activePointerIndex = event.findPointerIndex(mActivePointerId);
                    final float x = event.getX(activePointerIndex);
                    int deltaX = (int) (x - mDownX);
                    mDownX = x;

                    int scrollX = mPointChild.getScrollX();
                    // 手指拖动itemView滑动, deltaX大于0向右滑动,小于0向左滑动
                    if (deltaX > 0) {
                        if (scrollX > 0) {
                            // 右侧显示时
                            if (deltaX < scrollX) {
                                // 滑动的距离小于显示的距离
                                mPointChild.scrollBy(-deltaX, 0);
                            } else {
                                // 滑动的距离大于显示的距离, 滑动到头
                                mPointChild.scrollBy(-scrollX, 0);
                            }
                        }
                    } else if (deltaX < 0) {
                        if (scrollX < mSideWidth) {
                            //右侧未显示完全
                            int endWidth = mSideWidth - scrollX;// 剩余显示的距离
                            if (-deltaX < endWidth) {
                                // 滑动的距离小于剩余显示的距离
                                mPointChild.scrollBy(-deltaX, 0);
                            } else {
                                // 滑动的距离剩余显示的距离, 滑动到头
                                mPointChild.scrollBy(endWidth, 0);
                            }
                        }
                    }
                    //LogUtil.d("onTouchEvent ACTION_MOVE deltaX:" + deltaX + ",scrollX:" + scrollX);
                    return true;
                case MotionEvent.ACTION_UP:
                    int upScrollX = mPointChild.getScrollX();
                    //LogUtil.d("onTouchEvent ACTION_UP:" + upScrollX);
                    if (upScrollX >= mSideWidth / 2) {
                        // 滑动到显示右侧
                        scrollShowSide();
                    } else {
                        // 滑动到关闭右侧
                        turnNormal();
                    }
                    isSide = false;
                    //事件响应
                    requestDisallowInterceptTouchEvent(false);
                    mActivePointerId = INVALID_POINTER_ID;
                    break;
            }
        }

        return super.onTouchEvent(event);
    }

滑动的代码

    @Override
    public void computeScroll() {
        // 调用startScroll的时候scroller.computeScrollOffset()返回true,
        if (scroller.computeScrollOffset() && mPointChild != null) {
            // 让ListView item根据当前的滚动偏移量进行滚动
            mPointChild.scrollTo(scroller.getCurrX(), scroller.getCurrY());
            //LogUtil.d("computeScroll x:" + scroller.getCurrX() + "y:" + scroller.getCurrY());
            postInvalidate();

            // 滚动动画结束的时候调用回调接口
            /*if (scroller.isFinished()) {
                //mPointChild.scrollTo(0, 0);
            }*/
        }
    }

    /**
     * 转换到显示状态
     */
    public void scrollShowSide() {
        isSideShow = true;
        if (mPointChild == null) return;
        // 时间
        float delta = (float) (Math.abs(mSideWidth - mPointChild.getScrollX())) / (mSideWidth / 2f) * animDuration;
        // x偏移量
        int offset = mSideWidth - mPointChild.getScrollX();
        if (offset == 0) return;
        // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
        scroller.startScroll(mPointChild.getScrollX(), 0, offset, 0, (int) Math.abs(delta));
        postInvalidate(); // 刷新itemView
    }

    /**
     * 转换为正常隐藏情况
     */
    public void turnNormal() {
        isSideShow = false;
        if (mPointChild == null) return;
        // 时间
        float delta = (float) mPointChild.getScrollX() / (mSideWidth / 2f) * animDuration;
        // x偏移量
        int offset = -mPointChild.getScrollX();
        if (offset == 0) return;
        // 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
        scroller.startScroll(mPointChild.getScrollX(), 0, offset, 0, (int) Math.abs(delta));
        postInvalidate(); // 刷新itemView
    }

如果RecyclerView在ViewPager中的话需要特殊处理,自定义一个可以设置是否可以滑动的ViewPager,当侧滑时停止viewPager的滑动,主要代码如下


    /**
     * 锁定时禁止viewpager的滑动
     */
    private void onLocked() {
        ViewParent parent = getParent();
        CustomViewPager pager;
        while (parent != null) {
            if (parent instanceof CustomViewPager) {
                pager = (CustomViewPager) parent;
                pager.setTag(R.id.tag_custom_view_page, pager.getScrollable());
                pager.setScrollable(false);
            }
            parent = parent.getParent();
        }
    }

    /**
     * 解除锁定时
     */
    private void onUnLocked() {
        ViewParent parent = getParent();
        CustomViewPager pager;
        boolean scroll = true;
        while (parent != null) {
            if (parent instanceof CustomViewPager) {
                pager = (CustomViewPager) parent;
                if (pager.getTag(R.id.tag_custom_view_page) instanceof Boolean) {
                    scroll = (boolean) pager.getTag(R.id.tag_custom_view_page);
                }
                ((CustomViewPager) parent).setScrollable(scroll);
            }
            parent = parent.getParent();
        }
    }

参考源码链接,demo地址:单独的RecyclerView在ViewPager中的RecyclerView

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值