欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

RecyclerView源码学习笔记(四)滑动

程序员文章站 2022-06-14 11:17:19
...

前几篇学习了RecyclerView的初始化和绘制过程,主要情景都是在静止状态下,没有手动操作,这篇开始就学习在人为操作下的代码流程,先从滑动开始
- RecyclerView源码学习笔记(一)构造函数和setLayoutManager方法
- RecyclerView源码学习笔记(二)setAdapter
- RecyclerView源码学习笔记(三)RecycleView的绘制过程onMeasure,onLayout,onDraw

onInterceptTouchEvent

当一个触摸事件传递到RecyclerView的时候,会调用onInterceptTouchEvent方法,判断当前viewGroup需不需要拦截这个touch事件,如果要拦截就返回true,否则false,onInterceptTouchEvent的源码

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;
        }

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

        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, TYPE_TOUCH);
                break;

            case MotionEvent.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 = x;
                        startScroll = true;
                    }
                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
                        mLastTouchY = y;
                        startScroll = true;
                    }
                    if (startScroll) {
                        setScrollState(SCROLL_STATE_DRAGGING);
                    }
                }
            } break;

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

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

            case MotionEvent.ACTION_CANCEL: {
                cancelTouch();
            }
        }
        return mScrollState == SCROLL_STATE_DRAGGING;
    }

首先判断当前RecyclerView有没有被冻住,如果是则直接返回false,也就是说在frozen的情况下,RecyclerView的item还是可以收到touch事件。接下来调用dispatchOnItemTouchIntercept(e)方法,查看其返回值,如果是true,则取消当前touch事件,并返回true,看一下这个dispatchOnItemTouchIntercept(e)方法做了什么

    private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
        final int action = e.getAction();
        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
            mActiveOnItemTouchListener = null;
        }

        final int listenerCount = mOnItemTouchListeners.size();
        for (int i = 0; i < listenerCount; i++) {
            final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
            if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
                mActiveOnItemTouchListener = listener;
                return true;
            }
        }
        return false;
    }

从代码可以看出,其实就是去调用了所有注册的OnItemTouchListeneronInterceptTouchEvent()方法,如果有一个返回true,则直接返回true,这说明OnItemTouchListener的实现者可以拦截RecyclerView的touch事件不再往下传递。我们这里假设没有注册OnItemTouchListener,代码继续往下走。

直接跳到switch部分,我们假设现在的操作是一次从底部往顶部的拖动动作,那么事件序列,首先是ACTION_DOWN事件,在ACTION_DOWN这个case里主要记录了触摸点的位置,然后调用startNestedScroll方法,这个方法是嵌套滑动体系里的,读者有兴趣可以另外研究,这里不再说明。然后就跳出switch,返回值由mScrollState == SCROLL_STATE_DRAGGING是否成立确定,这里是false,因为我们在点击之前,RecycleView是静止的,没有滑动,所以mScroolStateSCROLL_STATE_IDLE

从onInterceptTouchEvent返回后,会调到onTouchEvent方法,这里再把源码贴上来

public boolean onTouchEvent(MotionEvent e) {
        if (mLayoutFrozen || mIgnoreMotionEventTillDown) {
            return false;
        }
        if (dispatchOnItemTouch(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();
        }
        boolean eventAddedToVelocityTracker = false;

        final MotionEvent vtev = MotionEvent.obtain(e);
        final int action = e.getActionMasked();
        final int actionIndex = e.getActionIndex();

        if (action == MotionEvent.ACTION_DOWN) {
            mNestedOffsets[0] = mNestedOffsets[1] = 0;
        }
        vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);

        switch (action) {
            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;

            case MotionEvent.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);
                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 (mScrollState != SCROLL_STATE_DRAGGING) {
                    boolean startScroll = false;
                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
                        if (dx > 0) {
                            dx -= mTouchSlop;
                        } else {
                            dx += mTouchSlop;
                        }
                        startScroll = true;
                    }
                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
                        if (dy > 0) {
                            dy -= mTouchSlop;
                        } else {
                            dy += mTouchSlop;
                        }
                        startScroll = true;
                    }
                    if (startScroll) {
                        setScrollState(SCROLL_STATE_DRAGGING);
                    }
                }

                if (mScrollState == SCROLL_STATE_DRAGGING) {
                    mLastTouchX = x - mScrollOffset[0];
                    mLastTouchY = y - 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;

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

            case MotionEvent.ACTION_UP: {
                mVelocityTracker.addMovement(vtev);
                eventAddedToVelocityTracker = true;
                mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
                final float xvel = canScrollHorizontally
                        ? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
                final float yvel = canScrollVertically
                        ? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
                if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
                    setScrollState(SCROLL_STATE_IDLE);
                }
                resetTouch();
            } break;

            case MotionEvent.ACTION_CANCEL: {
                cancelTouch();
            } break;
        }

        if (!eventAddedToVelocityTracker) {
            mVelocityTracker.addMovement(vtev);
        }
        vtev.recycle();

        return true;
    }

这里会先进到ACTION_DOWN这个case,这个里面做的事情也是记录了坐标,调用startNestedScroll方法。接下来再回到onInterceptTouchEvent方法,因为我们的动作是一个拖动的动作,所以在ACTION_DOWN之后会紧跟着ACTION_MOVE事件,在onInterceptTouchEvent的ACTION_MOVE case中主要进行了滑动距离的判断,如果距离大于touchSlop则认为是拖动事件,则将mScrollState设置为SCROLL_STATE_DRAGGING,这时候因为mScrollState已经设置成SCROLL_STATE_DRAGGING。所以onInterceptTouchEvent将会返回true,这样touch事件就不会再传给item

接下来又会调到onTouchEvent方法,并进入case ACTION_MOVE,在case ACTION_MOVE里最重要的就是如下代码段

 if (mScrollState == SCROLL_STATE_DRAGGING) {
     mLastTouchX = x - mScrollOffset[0];
     mLastTouchY = y - 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);
     }
 }

首先判断当前是不是在拖动,是的,然后就是调用scrollByInternal方法,这个方法内部会根据拖动的距离,添加item到RecyclerView,那么重点就研究这部分吧(省略部分代码)

 boolean scrollByInternal(int x, int y, MotionEvent ev) {
        int unconsumedX = 0, unconsumedY = 0;
        int consumedX = 0, consumedY = 0;

        consumePendingUpdateOperations();
        if (mAdapter != null) {
            eatRequestLayout();
            onEnterLayoutOrScroll();
            TraceCompat.beginSection(TRACE_SCROLL_TAG);
            fillRemainingScrollValues(mState);
            if (x != 0) {
                consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                unconsumedX = x - consumedX;
            }
            if (y != 0) {
                consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
                unconsumedY = y - consumedY;
            }
      。。。。
    }

做了如下事情

consumePendingUpdateOperations()将没有处理的item操作处理完,我们这里假设他没有,直接return
接下来直接跳到如下代码,因为是纵向滑动,所以y不等于0,然后调到mLayout.scrollVerticallyBy

 if (y != 0) {
    consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
    unconsumedY = y - consumedY;
}

mLayout.scrollVerticallyBy在LinearLayoutManager的实现主要就是调用了以下方法

    int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || dy == 0) {
            return 0;
        }
        mLayoutState.mRecycle = true;
        ensureLayoutState();
        final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        final int absDy = Math.abs(dy);
        updateLayoutState(layoutDirection, absDy, true, state);
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
        if (consumed < 0) {
            if (DEBUG) {
                Log.d(TAG, "Don't have any more elements to scroll");
            }
            return 0;
        }
        final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
        mOrientationHelper.offsetChildren(-scrolled);
        if (DEBUG) {
            Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
        }
        mLayoutState.mLastScrollDelta = scrolled;
        return scrolled;
    }

这里的dy是上次的点击位置减去这次的点击位置,如果大于0说明是从底部往顶部滑。所以这里layoutDirection的值就是LayoutState.LAYOUT_ENDupdateLayoutState()方法是在放item前做一些准备工作,代码如下

    private void updateLayoutState(int layoutDirection, int requiredSpace,
            boolean canUseExistingSpace, RecyclerView.State state) {
        // If parent provides a hint, don't measure unlimited.
        mLayoutState.mInfinite = resolveIsInfinite();
        mLayoutState.mExtra = getExtraLayoutSpace(state);
        mLayoutState.mLayoutDirection = layoutDirection;
        int scrollingOffset;
        if (layoutDirection == LayoutState.LAYOUT_END) {
            mLayoutState.mExtra += mOrientationHelper.getEndPadding();
            // get the first child in the direction we are going
            final View child = getChildClosestToEnd();
            // the direction in which we are traversing children
            mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
                    : LayoutState.ITEM_DIRECTION_TAIL;
            mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
            mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
            // calculate how much we can scroll without adding new children (independent of layout)
            scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
                    - mOrientationHelper.getEndAfterPadding();

        } else {
           ...
        }
        mLayoutState.mAvailable = requiredSpace;
        if (canUseExistingSpace) {
            mLayoutState.mAvailable -= scrollingOffset;
        }
        mLayoutState.mScrollingOffset = scrollingOffset;
    }
  • mLayoutState.mInfinite表示当前RecyclerView对加载的item有没有数量限制
  • mLayoutState.mExtra表示提前加载那些还没有显示到屏幕上的item,一般是提前一个屏幕的长度,单位是px
  • 因为我们这里layoutDirection等于LayoutState.LAYOUT_END,所以进入第一个if,先是给mLayoutState.mExtra加上底部padding的长度,因为有时候我们会给RecyclerView设置padding,接着找到屏幕上最靠近底部的那个item。
  • mLayoutState.mItemDirection的意思是指我们在添加itemview的时候,从adapter去取对应数据的时候,是从头开始取还是倒着取,我们这里不做特殊处理,所以是顺着取,mLayoutState.mItemDirection = LayoutState.ITEM_DIRECTION_TAIL
  • mLayoutState.mCurrentPosition是指当前要添加的item对应到adapter中的位置
  • mLayoutState.mOffset表示开始画item的坐标位置,单位是像素
  • scrollingOffset表示的值是屏幕上最后一个item的下边缘的坐标减去RecycleView的下边缘坐标再减去底部padding的值,这个值表示的意思是,如果我们不添加item,那我们还可以滑动多少距离,如下图,蓝色代表RecyclerView,红色代表item,绿色代表RecyclerView的底部坐标减去buttom padding后的坐标,scrollingOffse的值就是第二个item的下边缘坐标减去绿色线的坐标的差。
    RecyclerView源码学习笔记(四)滑动
  • mLayoutState.mAvailable代表滑动的距离,如果canUseExistingSpacetrue,则mLayoutState.mAvailable再减去scrollingOffset,说明需要绘制填充的距离更短了,最后将scrollingOffset赋值给mLayoutState.mScrollingOffset

我们再回到scrollBy()方法,在调用完updateLayoutState()方法后会调用fill()方法,源码如下

 int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        // max offset we should set is mFastScroll + available
        final int start = layoutState.mAvailable;
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            // TODO ugly bug fix. should not happen
            if (layoutState.mAvailable < 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }
        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            if (VERBOSE_TRACING) {
                TraceCompat.beginSection("LLM LayoutChunk");
            }
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if (VERBOSE_TRACING) {
                TraceCompat.endSection();
            }
            if (layoutChunkResult.mFinished) {
                break;
            }
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
            /**
             * Consume the available space if:
             * * layoutChunk did not request to be ignored
             * * OR we are laying out scrap children
             * * OR we are not doing pre-layout
             */
            if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                    || !state.isPreLayout()) {
                layoutState.mAvailable -= layoutChunkResult.mConsumed;
                // we keep a separate remaining space because mAvailable is important for recycling
                remainingSpace -= layoutChunkResult.mConsumed;
            }

            if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
            if (stopOnFocusable && layoutChunkResult.mFocusable) {
                break;
            }
        }
        if (DEBUG) {
            validateChildOrder();
        }
        return start - layoutState.mAvailable;
    }

这边会判断layoutState.mAvailable是否小于0,如果是,就设置回原来的值,也就是在减去mScrollingOffset之前的值,这里是什么目的还不清楚,难道是因为layoutState.mAvailable不可以为负数?接下来会调用recycleByLayoutState()方法,将在接下来被滑出屏幕的item回收,就是将item view全部remove,并回收到cache或者pool中。

然后调用while循环添加item,循环继续的条件是item还没有加载完或者RecyclerView可以无限加载且当前还没有加载满需要填充的空间。具体实施item加载的是layoutChunk()方法,源码如下:

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        View view = layoutState.next(recycler);
        if (view == null) {
            if (DEBUG && layoutState.mScrapList == null) {
                throw new RuntimeException("received null view when unexpected");
            }
            // if we are laying out views in scrap, this may return null which means there is
            // no more items to layout.
            result.mFinished = true;
            return;
        }
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else {
            top = getPaddingTop();
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }
        // We calculate everything with View's bounding box (which includes decor and margins)
        // To calculate correct layout position, we subtract margins.
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        if (DEBUG) {
            Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                    + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                    + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
        }
        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.hasFocusable();
    }

首先会调用layoutState.next(recycler)方法获取到一个item view,这个next方法源码如下:

  View next(RecyclerView.Recycler recycler) {
      if (mScrapList != null) {
          return nextViewFromScrapList();
      }
      final View view = recycler.getViewForPosition(mCurrentPosition);
      mCurrentPosition += mItemDirection;
      return view;
  }

先会判断mScrapList是不是null,这个对象只有在layoutForPredictiveAnimations方法中会被赋值,而layoutForPredictiveAnimations内部会判断要不要直接返回,我们这里现在是没有PredictiveAnimation所以layoutForPredictiveAnimations方法会直接返回,也就是mScrapList值等于null,这样的话就是会通过recycler.getViewForPosition方法获取itemview,getViewForPosition方法最后会调用到tryGetViewHolderForPositionByDeadline方法,此方法的第三个参数是一个long型的,表示需要在多长时间内完成item的创建和bind工作,如果是FOREVER_NS,表示没有时间限制,否则,就必须在规定时间内完成,否则就有可能返回null或者一个没有bind过的itemview。这里我们传进去的是FOREVER_NS,所以没有时间限制。tryGetViewHolderForPositionByDeadline方法内部会依次从mChangedScrap,mAttachedScrap,mCachedViews,mViewCacheExtension,RecycledViewPool去获取itemview,如果都没有获取到,则会调用adapter的createViewHolder方法来创建,创建view的代码如下

 if (holder == null) {
      long start = getNanoTime();
      if (deadlineNs != FOREVER_NS
              && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
          // abort - we have a deadline we can't meet
          return null;
      }
      holder = mAdapter.createViewHolder(RecyclerView.this, type);
      if (ALLOW_THREAD_GAP_WORK) {
          // only bother finding nested RV if prefetching
          RecyclerView innerView = findNestedRecyclerView(holder.itemView);
          if (innerView != null) {
              holder.mNestedRecyclerView = new WeakReference<>(innerView);
          }
      }

      long end = getNanoTime();
      mRecyclerPool.factorInCreateTime(type, end - start);
      if (DEBUG) {
          Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
      }
  }

可以看到最开始会判断deadlineNs是不是等于FOREVER_NS,如果不是就会先预测在规定时间内是不是可以完成创建,如果不能则直接返回null了,这里的预测时间是根据前面创建item的平均时间来确定的。在createViewHolder方法内部会调用Adapter子类实现的onCreateViewHolder方法

public final VH createViewHolder(ViewGroup parent, int viewType) {
    TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
    final VH holder = onCreateViewHolder(parent, viewType);
    holder.mItemViewType = viewType;
    TraceCompat.endSection();
    return holder;
}

然后接下来有如下代码:

   boolean bound = false;
  if (mState.isPreLayout() && holder.isBound()) {
      // do not update unless we absolutely have to.
      holder.mPreLayoutPosition = position;
  } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
      if (DEBUG && holder.isRemoved()) {
          throw new IllegalStateException("Removed holder should be bound and it should"
                  + " come here only in pre-layout. Holder: " + holder
                  + exceptionLabel());
      }
      final int offsetPosition = mAdapterHelper.findPositionOffset(position);
      bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
  }

因为我们这里没有prelayout,所以会走elseif,其中会判断viewholder是不是需要重新bind,如果是就调用tryBindViewHolderByDeadline方法,需要bind的条件有三个,`.没有bind过,2.需要更新,3.当前holder已经不可用

private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
       int position, long deadlineNs) {
   holder.mOwnerRecyclerView = RecyclerView.this;
   final int viewType = holder.getItemViewType();
   long startBindNs = getNanoTime();
   if (deadlineNs != FOREVER_NS
           && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
       // abort - we have a deadline we can't meet
       return false;
   }
   mAdapter.bindViewHolder(holder, offsetPosition);
   long endBindNs = getNanoTime();
   mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
   attachAccessibilityDelegateOnBind(holder);
   if (mState.isPreLayout()) {
       holder.mPreLayoutPosition = position;
   }
   return true;
}

方法内部也会先判断能不能在要求的时间内完成bind,如果不能就返回false,我能这里是FOREVER_NS,所以会调用
mAdapter.bindViewHolder(holder,offsetPosition),在bindViewHolder()内部会调用Adapter子类的实现的onBindViewHolder()方法

 public final void bindViewHolder(VH holder, int position) {
            holder.mPosition = position;
            if (hasStableIds()) {
                holder.mItemId = getItemId(position);
            }
            holder.setFlags(ViewHolder.FLAG_BOUND,
                    ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
                            | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
            TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
            onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
            holder.clearPayload();
            final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
            if (layoutParams instanceof RecyclerView.LayoutParams) {
                ((LayoutParams) layoutParams).mInsetsDirty = true;
            }
            TraceCompat.endSection();
        }

再回到tryGetViewHolderForPositionByDeadline()方法,最后该方法会给viewholder设置一个LayoutParams,如果viewholder当前没有LayoutParams,就会调用LayoutManager的generateDefaultLayoutParams()方法来生成一个默认的LayoutParams,此方法需要子类实现。

再回到layoutChunk()方法,再贴一下代码

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        View view = layoutState.next(recycler);
        if (view == null) {
            if (DEBUG && layoutState.mScrapList == null) {
                throw new RuntimeException("received null view when unexpected");
            }
            // if we are laying out views in scrap, this may return null which means there is
            // no more items to layout.
            result.mFinished = true;
            return;
        }
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else {
            top = getPaddingTop();
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }
        // We calculate everything with View's bounding box (which includes decor and margins)
        // To calculate correct layout position, we subtract margins.
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        if (DEBUG) {
            Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                    + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                    + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
        }
        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.hasFocusable();
    }

此时我们已经从next()方法获取到了一个view,此view不等于null,然后按照我们现在的场景,会进到addView(view)方法。此方法最终会调用到LayoutManager的addViewInt方法,此方法源码如下

private void addViewInt(View child, int index, boolean disappearing) {
    final ViewHolder holder = getChildViewHolderInt(child);
    if (disappearing || holder.isRemoved()) {
        // these views will be hidden at the end of the layout pass.
        mRecyclerView.mViewInfoStore.addToDisappearedInLayout(holder);
    } else {
        // This may look like unnecessary but may happen if layout manager supports
        // predictive layouts and adapter removed then re-added the same item.
        // In this case, added version will be visible in the post layout (because add is
        // deferred) but RV will still bind it to the same View.
        // So if a View re-appears in post layout pass, remove it from disappearing list.
        mRecyclerView.mViewInfoStore.removeFromDisappearedInLayout(holder);
    }
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    if (holder.wasReturnedFromScrap() || holder.isScrap()) {
        if (holder.isScrap()) {
            holder.unScrap();
        } else {
            holder.clearReturnedFromScrapFlag();
        }
        mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
        if (DISPATCH_TEMP_DETACH) {
            ViewCompat.dispatchFinishTemporaryDetach(child);
        }
    } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
        // ensure in correct position
        int currentIndex = mChildHelper.indexOfChild(child);
        if (index == -1) {
            index = mChildHelper.getChildCount();
        }
        if (currentIndex == -1) {
            throw new IllegalStateException("Added View has RecyclerView as parent but"
                    + " view is not a real child. Unfiltered index:"
                    + mRecyclerView.indexOfChild(child) + mRecyclerView.exceptionLabel());
        }
        if (currentIndex != index) {
            mRecyclerView.mLayout.moveView(currentIndex, index);
        }
    } else {
        mChildHelper.addView(child, index, false);
        lp.mInsetsDirty = true;
        if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
            mSmoothScroller.onChildAttachedToWindow(child);
        }
    }
    if (lp.mPendingInvalidate) {
        if (DEBUG) {
            Log.d(TAG, "consuming pending invalidate on child " + lp.mViewHolder);
        }
        holder.itemView.invalidate();
        lp.mPendingInvalidate = false;
    }
}

做了如下事情:

  1. 判断需要添加的view的viewholder是需要消失的或者被移除的,两者满足一个就将此viewholder添加到ViewInfoState的mLayoutHolderMap中,用于之后的动画。
  2. 判断viewholder是不是scrap,如果是则清除相关标志,并将view重新attach到RecyclerView
  3. 如果当前view的parent是当前RecyclerView,也就是说没有dettach,则将此view移动到指定的index,这个过程是dettach到attach的过程
  4. 如果不满足2,3两种情况,则这个view是新建的view,则将此view add到RecyclerView,如果此view的位置是mSmoothScroller滑动的目标位置,则将targetview设为此view
  5. 最后如果此view需要刷新,也就是有重新bind,则调用invalidate()

layoutChunk()方法在调用完addview()方法后,会调用measureChildWithMargins()方法,此方法会用标准测量方法测量被添加的view,并将RecyclerView的滑动方向和itemdecoration考虑进去,这里不再分析源码。接下来把此item view消耗掉的空间数据保存到result的mConsumed变量中。接下来会根据之前的测量结果,决定view的摆放位置,也就是调用layout()方法,此过程中会将itemdecoration占用的空间也考虑进去。最后如果此view是被removed或者changed,则会忽略掉它所消耗的空间,因为remove的item不会消耗空间,反倒会增加空间,change的item所占空间不变,不会影响后续空间消耗的计算,这里的空间指的是根据用户滑动距离,RecyclerView需要填充的空间,也就是mLayoutState.mAvailable。到这里,·layoutChunk()·就结束了,接下来就又要回到fill()方法。再贴一遍code

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
           RecyclerView.State state, boolean stopOnFocusable) {
       // max offset we should set is mFastScroll + available
       final int start = layoutState.mAvailable;
       if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
           // TODO ugly bug fix. should not happen
           if (layoutState.mAvailable < 0) {
               layoutState.mScrollingOffset += layoutState.mAvailable;
           }
           recycleByLayoutState(recycler, layoutState);
       }
       int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
       LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
       while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
           layoutChunkResult.resetInternal();
           if (VERBOSE_TRACING) {
               TraceCompat.beginSection("LLM LayoutChunk");
           }
           layoutChunk(recycler, state, layoutState, layoutChunkResult);
           if (VERBOSE_TRACING) {
               TraceCompat.endSection();
           }
           if (layoutChunkResult.mFinished) {
               break;
           }
           layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
           /**
            * Consume the available space if:
            * * layoutChunk did not request to be ignored
            * * OR we are laying out scrap children
            * * OR we are not doing pre-layout
            */
           if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                   || !state.isPreLayout()) {
               layoutState.mAvailable -= layoutChunkResult.mConsumed;
               // we keep a separate remaining space because mAvailable is important for recycling
               remainingSpace -= layoutChunkResult.mConsumed;
           }

           if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
               layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
               if (layoutState.mAvailable < 0) {
                   layoutState.mScrollingOffset += layoutState.mAvailable;
               }
               recycleByLayoutState(recycler, layoutState);
           }
           if (stopOnFocusable && layoutChunkResult.mFocusable) {
               break;
           }
       }
       if (DEBUG) {
           validateChildOrder();
       }
       return start - layoutState.mAvailable;
   }

在调用完layoutChunk()方法之后,fill()继续做了如下事情:

  1. 判断此次填充动作是不是结束,也就是看layoutChunkResult.mFinished这个变量,如果结束则break,我们这里假设它没有结束
  2. 接下来会根据item的填充方向和item消耗的空间更新layoutState.mOffset
  3. 然后更新layoutState.mAvailable,更新此变量的条件是:1.所添加的view不需要忽略,2.正在布局scrap的view,3.没有进行pre-layout.
  4. 更新layoutState.mScrollingOffset,并调用recycleByLayoutState()方法回收暂时会被移除屏幕的item
  5. 返回已经消耗的空间,单位是px

到这里fill()方法就结束了,再回到scrollBy()方法,判断返回的consumer是否小于0,小于0说明已经没有item可以再添加,直接返回0(其实这部分不明白,为什么consumer小于0就是没有item需要再添加),否则就根据scroll的像素个数来移动child views的位置,实现滑动效果,然后将scroll的距离保存在mLayoutState.mLastScrollDelta,最后返回scroll。
scroll结束后再回到scrollByInternal()方法,后续的代码如下:

    if (y != 0) {
         consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
         unconsumedY = y - consumedY;
     }
     ...
     repositionShadowingViews();
     ...
 }
 if (!mItemDecorations.isEmpty()) {
     invalidate();
 }

 if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
         TYPE_TOUCH)) {
     // Update the last touch co-ords, taking any scroll offset into account
     mLastTouchX -= mScrollOffset[0];
     mLastTouchY -= mScrollOffset[1];
     if (ev != null) {
         ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
     }
     mNestedOffsets[0] += mScrollOffset[0];
     mNestedOffsets[1] += mScrollOffset[1];
 } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
     if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) {
         pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
     }
     considerReleasingGlowsOnScroll(x, y);
 }
 if (consumedX != 0 || consumedY != 0) {
     dispatchOnScrolled(consumedX, consumedY);
 }
 if (!awakenScrollBars()) {
     invalidate();
 }
 return consumedX != 0 || consumedY != 0;

接下来会调用repositionShadowingViews方法,此方法里做的事情和itemChange的动画有关,我们这里先不涉及动画。然后判断itemdecoration是否有指定,如果有的话就重绘RecyclerView。后面就是嵌套滑动和边缘阴影的动作,然后再调用各个监听scroll的listener,不再深究,最后返回是否有滑动被消耗掉。到这里scrollByInternal()也结束了,终于又回到onTouchEvent了,在onTouchEvent会判断滑动是否有被消耗,如果有的话,就会调用getParent().requestDisallowInterceptTouchEvent(true)方法来阻止RecyclerView的父控件来拦截touch事件,说明RecyclerView需要继续处理后续事件。case Move后面的代码就不讲了,比较简单,都是一些资源回收,状态重置的动作。

最后就是调用onTouchEvent方法中Case Up的代码,这里主要是做了一个fling()的动作也就是快速滑动,这部分代码这里不讲了,等后面有机会再看,其实做的事情和case move差不多。

好了,滑动部分就到这里,我也是在学习源码,有些地方如果有错误还请各位读者提出