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