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

前几篇学习了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的下边缘坐标减去绿色线的坐标的差。
    这里写图片描述
  • 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差不多。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值