Android--RecyclerView嵌套RecyclerV

2020-02-28  本文已影响0人  aruba
我们在使用RecyclerView嵌套RecyclerView时,会觉得卡顿,操作不是很顺畅
RecyclerView嵌套RecyclerView滑动.gif
在这个现象中,我们想要水平滑动时,却竖直滑动了
我们观察RecyclerView的源码,在onInterceptTouchEvent方法中对事件做了处理
    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        if (mLayoutFrozen) {
            // When layout is frozen,  RV does not intercept the motion event.
            // A child view e.g. a button may still get the click.
            return false;
        }
        if (dispatchOnItemTouchIntercept(e)) {
            cancelTouch();
            return true;
        }

        if (mLayout == null) {
            return false;
        }

        final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
        final boolean canScrollVertically = mLayout.canScrollVertically();

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(e);

        final int action = MotionEventCompat.getActionMasked(e);
        final int actionIndex = MotionEventCompat.getActionIndex(e);

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                if (mIgnoreMotionEventTillDown) {
                    mIgnoreMotionEventTillDown = false;
                }
                mScrollPointerId = e.getPointerId(0);
                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);

                if (mScrollState == SCROLL_STATE_SETTLING) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                    setScrollState(SCROLL_STATE_DRAGGING);
                }

                // Clear the nested offsets
                mNestedOffsets[0] = mNestedOffsets[1] = 0;

                int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
                if (canScrollHorizontally) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
                }
                if (canScrollVertically) {
                    nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
                }
                startNestedScroll(nestedScrollAxis);
                break;

            case MotionEventCompat.ACTION_POINTER_DOWN:
                mScrollPointerId = e.getPointerId(actionIndex);
                mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
                mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
                break;

            case MotionEvent.ACTION_MOVE: {
                final int index = e.findPointerIndex(mScrollPointerId);
                if (index < 0) {
                    Log.e(TAG, "Error processing scroll; pointer index for id " +
                            mScrollPointerId + " not found. Did any MotionEvents get skipped?");
                    return false;
                }

                final int x = (int) (e.getX(index) + 0.5f);
                final int y = (int) (e.getY(index) + 0.5f);
                if (mScrollState != SCROLL_STATE_DRAGGING) {
                    final int dx = x - mInitialTouchX;
                    final int dy = y - mInitialTouchY;
                    boolean startScroll = false;
                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                        mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
                        startScroll = true;
                    }
                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
                        mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
                        startScroll = true;
                    }
                    if (startScroll) {
                        setScrollState(SCROLL_STATE_DRAGGING);
                    }
                }
            } break;

            case MotionEventCompat.ACTION_POINTER_UP: {
                onPointerUp(e);
            } break;

            case MotionEvent.ACTION_UP: {
                mVelocityTracker.clear();
                stopNestedScroll();
            } break;

            case MotionEvent.ACTION_CANCEL: {
                cancelTouch();
            }
        }
        return mScrollState == SCROLL_STATE_DRAGGING;
    }
主要看MOVE事件,RecyclerView中只做了距离判断,只要滑动的距离大于系统的默认滑动距离阈值,则本身进行滑动,这就导致了为什么我们水平滑动的时候,子RecyclerView接收不到Touch事件的原因,我们针对这点进行优化
/**
 * 解决嵌套RecyclerView滑动问题
 */
public class BetterRecyclerView extends RecyclerView {
    private int mTouchSlop;
    private int mScrollPointerId = 0;
    private int mInitialTouchX;
    private int mInitialTouchY;

    public BetterRecyclerView(@NonNull Context context) {
        this(context, null);
    }

    public BetterRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BetterRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        final ViewConfiguration vc = ViewConfiguration.get(context);
        mTouchSlop = vc.getScaledTouchSlop();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        //仿造源码
        final int action = MotionEventCompat.getActionMasked(e);
        final int actionIndex = MotionEventCompat.getActionIndex(e);

        switch (action) {
            case MotionEvent.ACTION_DOWN://记录按下的坐标
                mScrollPointerId = e.getPointerId(0);
                mInitialTouchX = (int) (e.getX() + 0.5f);
                mInitialTouchY = (int) (e.getY() + 0.5f);
                break;
            case MotionEvent.ACTION_POINTER_DOWN://这边处理多手指触摸,优先使用后面触摸的手指
                mScrollPointerId = e.getPointerId(actionIndex);
                mInitialTouchX = (int) (e.getX() + 0.5f);
                mInitialTouchY = (int) (e.getY() + 0.5f);
                break;
            case MotionEvent.ACTION_MOVE://仿造源码
                final int index = e.findPointerIndex(mScrollPointerId);
                final int x = (int) (e.getX(index) + 0.5f);
                final int y = (int) (e.getY(index) + 0.5f);

                boolean startScroll = false;
                if (getScrollState() != SCROLL_STATE_DRAGGING) {
                    final int dx = x - mInitialTouchX;
                    final int dy = y - mInitialTouchY;

                    //这边增加拦截的难度,当滑动的水平距离大于滑动的垂直距离时,才拦截,当然了,如果自身又能竖直滑动的情况,就不判断两个滑动距离大小
                    if ((getLayoutManager().canScrollHorizontally() && Math.abs(dx) > mTouchSlop)
                            && (getLayoutManager().canScrollVertically() || Math.abs(dx) > Math.abs(dy))) {
                        startScroll = true;
                    }
                    //这里是当滑动的水平距离小于滑动的垂直距离时,才拦截,如果自身又能水平滑动的情况,就不判断两个滑动距离大小
                    if ((getLayoutManager().canScrollVertically() && Math.abs(dy) > mTouchSlop)
                            && (getLayoutManager().canScrollHorizontally() || Math.abs(dy) > Math.abs(dx))) {
                        startScroll = true;
                    }
                }

                return startScroll && super.onInterceptTouchEvent(e);
        }

        return super.onInterceptTouchEvent(e);
    }
}
我们修改了拦截方法,只有当水平滑动距离大于竖直滑动距离时,才让RecyclerView拦截事件
RecyclerView嵌套RecyclerView滑动2.gif
这时,发现了另一个问题,当子RecyclerView快速滑动,还没有停止滚动时,我们去竖直滑动,发现父RecyclerView没法接受到Touch事件,我们找到requestDisallowInterceptTouchEvent方法并改写了它,解决了这个问题
    /**
     * 这个接口是不允许父类打断这个onTouch事件,一般被子类调用getParent.requestDisallowInterceptTouchEvent(true)
     * 比如快速水平滑动子view的时候,这时我们想去竖直滑动父类,因为快速滑动有一个速度,子view还没停止滚动,所以子类禁止父类拦截
     * 想要解决这个办法,就把这个方法改为空方法就行了
     *
     * @param disallowIntercept
     */
    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
RecyclerView嵌套RecyclerView滑动3.gif
上一篇下一篇

猜你喜欢

热点阅读