美文网首页
Android--RecyclerView嵌套RecyclerV

Android--RecyclerView嵌套RecyclerV

作者: aruba | 来源:发表于2020-02-28 10:35 被阅读0次
    我们在使用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

    相关文章

      网友评论

          本文标题:Android--RecyclerView嵌套RecyclerV

          本文链接:https://www.haomeiwen.com/subject/chskhhtx.html