RecyclerView滑动

作者: leap_ | 来源:发表于2020-06-18 16:32 被阅读0次

    嵌套滑动的实现

    RecyclerView结合CoordinatorLayout可以完成一个上滑悬浮(吸顶效果)的效果,这是因为它们实现了NestedScrollingChildNestedScrollingParent接口,并配合NestedScrollingChildHelperNestedScrollingParentHelper完成嵌套滑动的分工;

    1. NestedScrollingChild接口:

    嵌套滑动的孩子必须实现这个接口,RecyclerView就实现了这个接口;

    • startNestedScroll:开始嵌套滑动
    • dispatchNestedPreScroll(dispatchNestedPreFling):孩子开始滑动前调用
    • dispatchNestedScroll(dispatchNestedFling):还是滑动后调用
    • stopNestedScroll:结束嵌套滑动

    Fling是快速滑动,即手指离开后还会根据计算的加速度继续滑动

    2. NestedScrollingParent接口:

    嵌套滑动的父亲实现这个接口,CoordinatorLayout实现了这个接口;

    public interface NestedScrollingParent {
        public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
        public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
        public void onStopNestedScroll(View target);
        public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
                int dxUnconsumed, int dyUnconsumed);
        public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
        public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
        public boolean onNestedPreFling(View target, float velocityX, float velocityY);
        public int getNestedScrollAxes();
    }
    

    在parent接口中主要是child接口的回调,名称都是类似的;比如在开始滑动时,child调用startNestedScroll,那么在这个方法里,会回调parent的onStartNestedScroll,根据parent的返回值,child再做进一步处理;

    RV的TouchEvent

        @Override
        public boolean onTouchEvent(MotionEvent e) {
    ...
            if (dispatchOnItemTouch(e)) {
                cancelTouch();
                return true;
            }
    ....
          }
    

    首先调用dispatchOnItemTouch()交给item处理,如果item不消费当前事件才会进入下面的滑动处理

    Down事件:

    case MotionEvent.ACTION_DOWN: {
                    mScrollPointerId = e.getPointerId(0);
                    mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
                    mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
    
                    int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
                    if (canScrollHorizontally) {
                        nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
                    }
                    if (canScrollVertically) {
                        nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
                    }
                    startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
                } break;
    
    • 记录当前手指开始的坐标mInitialTouchX , mInitialTouchY
    • 调用嵌套滑动接口的startNestedScroll()询问父亲是否处理(即调用parent的onStartNestedScroll()具体的传递通过childHelper完成)
    Down 事件

    Move事件:

                case MotionEvent.ACTION_MOVE: {
    
                    final int x = (int) (e.getX(index) + 0.5f);
                    final int y = (int) (e.getY(index) + 0.5f);
                    int dx = mLastTouchX - x;
                    int dy = mLastTouchY - y;
    
                    if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
                        dx -= mScrollConsumed[0];
                        dy -= mScrollConsumed[1];
                        vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
                        // Updated the nested offsets
                        mNestedOffsets[0] += mScrollOffset[0];
                        mNestedOffsets[1] += mScrollOffset[1];
                    }
    
                        if (scrollByInternal(
                                canScrollHorizontally ? dx : 0,
                                canScrollVertically ? dy : 0,
                                vtev)) {
                            getParent().requestDisallowInterceptTouchEvent(true);
                        }
    
                        if (mGapWorker != null && (dx != 0 || dy != 0)) {
                            mGapWorker.postFromTraversal(this, dx, dy);
                        }
                    }
                } break;
    
    • 记录滑动的距离dx , dy
    • 调用嵌套滑动接口dispatchNestedPreScroll()通知父亲回调onNestedPreScroll()
    • 调用scrollByInternal()实现滑动的效果,(这个在RV缓存有详细展开)
    • 调用mGapWorker.postFromTraversal()来预加载
    Move事件
    mGapWorker:
        void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
            if (recyclerView.isAttachedToWindow()) {
                if (RecyclerView.DEBUG && !mRecyclerViews.contains(recyclerView)) {
                    throw new IllegalStateException("attempting to post unregistered view!");
                }
                if (mPostTimeNs == 0) {
                    mPostTimeNs = recyclerView.getNanoTime();
                    recyclerView.post(this);
                }
            }
        }
    
    • mGapWorker是一个Runnable,看一下run():
        @Override
        public void run() {
    ...
                prefetch(nextFrameNs);
    ...  
        }
    
        void prefetch(long deadlineNs) {
            flushTasksWithDeadline(deadlineNs);
        }
    
    • flushTasksWithDeadline()最终调用了tryGetViewHolderForPositionByDeadline()来预取ViewHolder,这是RV缓存的核心方法,(在RV缓存有详细介绍)

    UP事件:

                case MotionEvent.ACTION_UP: {
    
                    if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
                        setScrollState(SCROLL_STATE_IDLE);
                    }
    
                    resetTouch();
                } break;
    
    • resetTouch()当前滑动事件的收尾工作,包括stopNestedScroll(TYPE_TOUCH)通知父亲回调onstopNestedScroll()
    • 调用fling()判断是否是fling快速滑动
        public boolean fling(int velocityX, int velocityY) {
     
            if (!dispatchNestedPreFling(velocityX, velocityY)) {
                final boolean canScroll = canScrollHorizontal || canScrollVertical;
                dispatchNestedFling(velocityX, velocityY, canScroll);
    
                if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) {
                    return true;
                }
    
                    mViewFlinger.fling(velocityX, velocityY);
                    return true;
                }
            }
            return false;
        }
    
    
            public void fling(int velocityX, int velocityY) {
                setScrollState(SCROLL_STATE_SETTLING);
                mLastFlingX = mLastFlingY = 0;
                mScroller.fling(0, 0, velocityX, velocityY,
                        Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
                postOnAnimation();
            }
    
              void postOnAnimation() {
                    ViewCompat.postOnAnimation(RecyclerView.this, this);
            }
    

    在fling()中判断如果是fling快速滑动:

    • 调用嵌套滑动接口dispatchNestedPreFling() , dispatchNestedFling()
    • 调用OverScroller.fling()计算快速滑动的距离,通过postOnAnimation()运行RV的ViewFlinger.run()(提交给Handler)完成快速滑动,下面我们看run()做的事:
            @Override
            public void run() {
    
                final OverScroller scroller = mScroller;
                final SmoothScroller smoothScroller = mLayout.mSmoothScroller;
    
                if (dispatchNestedPreScroll(dx, dy, scrollConsumed, null, TYPE_NON_TOUCH)) {
                        dx -= scrollConsumed[0];
                        dy -= scrollConsumed[1];
                    }
    
                        if (dx != 0) {
                            hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
                            overscrollX = dx - hresult;
                        }
                        if (dy != 0) {
                            vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
                            overscrollY = dy - vresult;
                        }
    
    
                        if (smoothScroller != null && !smoothScroller.isPendingInitialRun()
                                && smoothScroller.isRunning()) {
                            final int adapterSize = mState.getItemCount();
                            if (adapterSize == 0) {
                                smoothScroller.stop();
                            } else if (smoothScroller.getTargetPosition() >= adapterSize) {
                                smoothScroller.setTargetPosition(adapterSize - 1);
                                smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
                            } else {
                                smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
                            }
                        }
                    }
    
    
    
                    if (hresult != 0 || vresult != 0) {
                        dispatchOnScrolled(hresult, vresult);
                    }
    
    
                    if (scroller.isFinished() || (!fullyConsumedAny
                            && !hasNestedScrollingParent(TYPE_NON_TOUCH))) {
                        // setting state to idle will stop this.
                        setScrollState(SCROLL_STATE_IDLE);
                        if (ALLOW_THREAD_GAP_WORK) {
                            mPrefetchRegistry.clearPrefetchPositions();
                        }
                        stopNestedScroll(TYPE_NON_TOUCH);
                    } else {
                        postOnAnimation();
                        if (mGapWorker != null) {
                            mGapWorker.postFromTraversal(RecyclerView.this, dx, dy);
                        }
                    }
                }
                // call this after the onAnimation is complete not to have inconsistent callbacks etc.
                if (smoothScroller != null) {
                    if (smoothScroller.isPendingInitialRun()) {
                        smoothScroller.onAnimation(0, 0);
                    }
                    if (!mReSchedulePostAnimationCallback) {
                        smoothScroller.stop(); //stop if it does not trigger any scroll
                    }
                }
            }
    
    • 滑动前调用嵌套滑动接口dispatchNestedPreScroll()通知父亲
    • mLayout.scrollHorizontallyBy()/scrollVerticallyBy()实现滑动,并配合RV内部类SmoothScroller实现平滑动画效果
    • 滑动后调用嵌套滑动接口dispatchOnScrolled();通知父亲
    • 在run()中递归调用postOnAnimation()防止没有滑动结束
    Up事件

    相关文章

      网友评论

        本文标题:RecyclerView滑动

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