RecyclerView OnBindViewHolder调用时机

1.第一条调用时机

初始化加载时会摆放时会调用onLayout,可是这个onLayout具体是什么时候被调用的?

recyclerView的onLayout的方法如下:

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    dispatchLayout();
    TraceCompat.endSection();
    mFirstLayoutComplete = true;
}

摆放时会调用这个方法,继续调用dispatchLayout,dispatchLayout的代码如下:

void dispatchLayout() {
        if (mAdapter == null) {
            Log.w(TAG, "No adapter attached; skipping layout");
            // leave the state in START
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "No layout manager attached; skipping layout");
            // leave the state in START
            return;
        }
        mState.mIsMeasuring = false;

        // If the last time we measured children in onMeasure, we skipped the measurement and layout
        // of RV children because the MeasureSpec in both dimensions was EXACTLY, and current
        // dimensions of the RV are not equal to the last measured dimensions of RV, we need to
        // measure and layout children one last time.
        boolean needsRemeasureDueToExactSkip = mLastAutoMeasureSkippedDueToExact
                        && (mLastAutoMeasureNonExactMeasuredWidth != getWidth()
                        || mLastAutoMeasureNonExactMeasuredHeight != getHeight());
        mLastAutoMeasureNonExactMeasuredWidth = 0;
        mLastAutoMeasureNonExactMeasuredHeight = 0;
        mLastAutoMeasureSkippedDueToExact = false;

        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates()
                || needsRemeasureDueToExactSkip
                || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            // First 2 steps are done in onMeasure but looks like we have to run again due to
            // changed size.

            // TODO(shepshapard): Worth a note that I believe
            //  "mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()" above is
            //  not actually correct, causes unnecessary work to be done, and should be
            //  removed. Removing causes many tests to fail and I didn't have the time to
            //  investigate. Just a note for the a future reader or bug fixer.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();
}

这个方法从方法名上面很好理解,就是把layout分发出去,其中关键性的代码如下:

if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates()
                || needsRemeasureDueToExactSkip
                || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {
            // First 2 steps are done in onMeasure but looks like we have to run again due to
            // changed size.

            // TODO(shepshapard): Worth a note that I believe
            //  "mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()" above is
            //  not actually correct, causes unnecessary work to be done, and should be
            //  removed. Removing causes many tests to fail and I didn't have the time to
            //  investigate. Just a note for the a future reader or bug fixer.
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            // always make sure we sync them (to ensure mode is exact)
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();

解释一下,几种状态:

(1)这个State的Step是STEP_START这个状态的时候,执行dispatchLayoutStep1(),之后执行dispatchLayoutStep2(),(问题:这个状态是干嘛的?为什么这个状态才执行?)

(2)如果有更新,宽高不等时会执行dispatchLayoutStep2(),

(3)其他情况下执行dispatchLayoutStep3(),(问题:这个方法里面又是做了什么?)

dispatchLayoutStep2的代码如下:

private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    mAdapterHelper.consumeUpdatesInOnePass();
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
    if (mPendingSavedState != null && mAdapter.canRestoreState()) {
        if (mPendingSavedState.mLayoutState != null) {
            mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState);
        }
        mPendingSavedState = null;
    }
    // Step 2: Run layout
    mState.mInPreLayout = false;
    mLayout.onLayoutChildren(mRecycler, mState);

    mState.mStructureChanged = false;

    // onLayoutChildren may have caused client code to disable item animations; re-check
    mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
}

这个方法也是一定会调用mLayout.onLayoutChildren(mRecycler, mState),调用mLayout的方法,因为这个变量是实际设置进去的,就是LayoutManager,LayoutManager是抽象类,是需要具体的实现的,这就是为什么RecyclerView在初始化时需要设置一个LayoutManager,不设置的时候是跑不通的,这只是一个原因,应该还有很多其他原因的代码调用,可以先随便找一个他的实现类,比如LinearLayoutManager这个类,就满足要求,看一下他的onLayoutChildren()方法里面执行了什么具体内容,这个方法由于过长,只截取部分内容,其余部分省略掉,

/**
* {@inheritDoc}
*/
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state){
    ……
    if (mAnchorInfo.mLayoutFromEnd) {
    	……
    	fill(recycler, mLayoutState, state, false);
    	……
    } else {
    	……
    	fill(recycler, mLayoutState, state, false);
    	……
    }
    ……
}

这个里面都会走到这个fill方法,

/**
* The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
* independent from the rest of the {@link LinearLayoutManager}
* and with little change, can be made publicly available as a helper class.
*
* @param recycler        Current recycler that is attached to RecyclerView
* @param layoutState     Configuration on how we should fill out the available space.
* @param state           Context passed by the RecyclerView to control scroll steps.
* @param stopOnFocusable If true, filling stops in the first focusable new child
* @return Number of pixels that it added. Useful for scroll functions.
*/
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
         RecyclerView.State state, boolean stopOnFocusable){
}

翻译如下:神奇的方法,填充给定的layout,根据layoutState设定的,这个方法可以设置成独立的静态方法,但是需要做一点点变动,可以用作一个help类。

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.mExtraFillSpace;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            if (RecyclerView.VERBOSE_TRACING) {
                TraceCompat.beginSection("LLM LayoutChunk");
            }
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if (RecyclerView.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 || layoutState.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;
    }

这里面有个while循环,判断条件是这个:

(layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)

第一个条件是或的关系,当前的layoutManager的属性是无限的情况下,这个mInfinite其实就是当前linearLayout的属性,因为他是从上面的方法一层一层传过来的,参数头其实就是当前这个类,或者剩余的空间>0,这个剩余空间是怎么计算的?

int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;

就是当前的可用的空间和剩余填充的空间之和,可用空间还好理解,剩余空间是干啥的呢?

/**
* Used if you want to pre-layout items that are not yet visible.
* The difference with {@link #mAvailable} is that, when recycling, distance laid out for
* {@link #mExtraFillSpace} is not considered to avoid recycling visible children.
*/
int mExtraFillSpace = 0;

这个属性是这样说的,他和mAvailable的区别就是:当回收的时候,这个空间填充的子项目是不会被回收的,因为这样可能会导致把可见的某些子项目回收掉。

那问题又来了:这个可用空间,和剩余填充的空间具体在屏幕上指的是啥?具体指的是哪块区域?我也不清楚,就当是挖坑吧,以后有机会再看。

后面&&的条件就比较好理解了,点开后发现是这样的:

/**
* @return true if there are more items in the data adapter
*/
boolean hasMore(RecyclerView.State state) {
	return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
}

这个方法比较好理解,就是这里还有更多的数据没加载完的时候,

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;
        }
        RecyclerView.LayoutParams params = (RecyclerView.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方法,这个next方法内容如下:

/**
* Gets the view for the next element that we should layout.
* Also updates current item index to the next item, based on {@link #mItemDirection}
*
* @return The next element that we should layout.
*/
View next(RecyclerView.Recycler recycler) {
    if (mScrapList != null) {
    return nextViewFromScrapList();
    }
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

这个里面就直接调用到getViewForPosition(mCurrentPosition)里面了,就回跳到recyclerView里面了。

@NonNull
public View getViewForPosition(int position) {
	return getViewForPosition(position, false);
}

这个是recyclerView里面的方法了,然后接着跳:

View getViewForPosition(int position, boolean dryRun) {
	return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

接着跳到里面的deadline方法,

@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs){
    ……
    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);
    }
    ……
}

这里面一定会走到tryBindViewHolderByDeadline这个方法,大概的意思就是最后要彻底的绑定view了,

private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, int position, long deadlineNs) {
    holder.mBindingAdapter = null;
    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;
}

再然后后面的方法就比较常见了,先是走到mAdapter.bindViewHolder(holder, offsetPosition)这个里面,依次只看代码就大概知道了,因为都是一定会执行的,没有判断条件,

* @param holder   The view holder whose contents should be updated
* @param position The position of the holder with respect to this adapter
* @see #onBindViewHolder(ViewHolder, int)
*/
public final void bindViewHolder(@NonNull VH holder, int position) {
    boolean rootBind = holder.mBindingAdapter == null;
    if (rootBind) {
    	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);
    }
    holder.mBindingAdapter = this;
    onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
    if (rootBind) {
        holder.clearPayload();
        final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
        if (layoutParams instanceof RecyclerView.LayoutParams) {
        	((LayoutParams) layoutParams).mInsetsDirty = true;
        }
        TraceCompat.endSection();
    }
}

后面再接着走到onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()里面,再然后就是

public void onBindViewHolder(@NonNull VH holder, int position,
@NonNull List<Object> payloads) {
	onBindViewHolder(holder, position);
}

再然后走到onBindViewHolder里面,

* @param holder   The ViewHolder which should be updated to represent the contents of the
* item at the given position in the data set.
* @param position The position of the item within the adapter's data set.
*/
public abstract void onBindViewHolder(@NonNull VH holder, int position);

走到这里的似乎后就是日常写代码时需要重写的方法了。

2.第二条调用时机

这个是从onTouchEvent里面滑动开始调用的,

@Override
public boolean onTouchEvent(MotionEvent e) {
	……
    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 (mScrollState != SCROLL_STATE_DRAGGING) {
                    boolean startScroll = false;
                    if (canScrollHorizontally) {
                        if (dx > 0) {
                            dx = Math.max(0, dx - mTouchSlop);
                        } else {
                            dx = Math.min(0, dx + mTouchSlop);
                        }
                        if (dx != 0) {
                            startScroll = true;
                        }
                    }
                    if (canScrollVertically) {
                        if (dy > 0) {
                            dy = Math.max(0, dy - mTouchSlop);
                        } else {
                            dy = Math.min(0, dy + mTouchSlop);
                        }
                        if (dy != 0) {
                            startScroll = true;
                        }
                    }
                    if (startScroll) {
                        setScrollState(SCROLL_STATE_DRAGGING);
                    }
                }

                if (mScrollState == SCROLL_STATE_DRAGGING) {
                    mReusableIntPair[0] = 0;
                    mReusableIntPair[1] = 0;
                    if (dispatchNestedPreScroll(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            mReusableIntPair, mScrollOffset, TYPE_TOUCH
                    )) {
                        dx -= mReusableIntPair[0];
                        dy -= mReusableIntPair[1];
                        // Updated the nested offsets
                        mNestedOffsets[0] += mScrollOffset[0];
                        mNestedOffsets[1] += mScrollOffset[1];
                        // Scroll has initiated, prevent parents from intercepting
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }

                    mLastTouchX = x - mScrollOffset[0];
                    mLastTouchY = y - mScrollOffset[1];

                    if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            e, TYPE_TOUCH)) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    if (mGapWorker != null && (dx != 0 || dy != 0)) {
                        mGapWorker.postFromTraversal(this, dx, dy);
                    }
                }
            }
            break;
            ……
        }
    ……
}

这个里面走到MotionEvent.ACTION_MOVE这个状态的时候,有个判断,if (mScrollState ==SCROLL_STATE_DRAGGING),当当前的状态是拖动的时候,里面会走到scrollByInternal()这个方法,这个方法再往里面跳,

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

        consumePendingUpdateOperations();
        if (mAdapter != null) {
            mReusableIntPair[0] = 0;
            mReusableIntPair[1] = 0;
            scrollStep(x, y, mReusableIntPair);
            consumedX = mReusableIntPair[0];
            consumedY = mReusableIntPair[1];
            unconsumedX = x - consumedX;
            unconsumedY = y - consumedY;
        }
        if (!mItemDecorations.isEmpty()) {
            invalidate();
        }

        mReusableIntPair[0] = 0;
        mReusableIntPair[1] = 0;
        dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
                type, mReusableIntPair);
        unconsumedX -= mReusableIntPair[0];
        unconsumedY -= mReusableIntPair[1];
        boolean consumedNestedScroll = mReusableIntPair[0] != 0 || mReusableIntPair[1] != 0;

        // Update the last touch co-ords, taking any scroll offset into account
        mLastTouchX -= mScrollOffset[0];
        mLastTouchY -= mScrollOffset[1];
        mNestedOffsets[0] += mScrollOffset[0];
        mNestedOffsets[1] += mScrollOffset[1];

        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 consumedNestedScroll || consumedX != 0 || consumedY != 0;
}

从这个方法里面再调用到scrollStep(x, y, mReusableIntPair)这个方法,再继续走,

void scrollStep(int dx, int dy, @Nullable int[] consumed) {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();

    TraceCompat.beginSection(TRACE_SCROLL_TAG);
    fillRemainingScrollValues(mState);

    int consumedX = 0;
    int consumedY = 0;
    if (dx != 0) {
    consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
    }
    if (dy != 0) {
    consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
    }

    TraceCompat.endSection();
    repositionShadowingViews();

    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);

    if (consumed != null) {
    consumed[0] = consumedX;
    consumed[1] = consumedY;
    }
}

这个方法里面的调用根据dx和dy区分是上下滑动还是左右滑动,dx和dy比较常见,就是正常滑动时的那几个参数,dy不等于0的话,那当前的recyclerView应该是垂直方向的拖动状态,fx不等于0的话,当前的recyclerView应该是水平方向的拖动状态。

接下来就是调用layoutManager里面的scrollVerticallyBy这个方法,这个layoutManager很多处用到,如果是vertical的方向,还是以linearLayoutManager举例,因为实际的情况下这个manager可能是别的,

@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
    if (mOrientation == HORIZONTAL) {
    	return 0;
    }
    return scrollBy(dy, recycler, state);
}

这个方法是linearLayoutManager重写父类的实现方法,再接着调用到scrollBy方法,

int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (getChildCount() == 0 || delta == 0) {
    	return 0;
    }
    ensureLayoutState();
    mLayoutState.mRecycle = true;
    final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
    final int absDelta = Math.abs(delta);
    updateLayoutState(layoutDirection, absDelta, 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 = absDelta > consumed ? layoutDirection * consumed : delta;
    mOrientationHelper.offsetChildren(-scrolled);
    if (DEBUG) {
    	Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled);
    }
    mLayoutState.mLastScrollDelta = scrolled;
    return scrolled;
}

这个里面还是会调用到fill方法,final int consumed = mLayoutState.mScrollingOffset+fill(recycler, mLayoutState, state, false);

但是这个计算具体计算的是什么?字面意思是已经消费的,但是具体是消费的哪些地方?暂时还不清楚。

走到fill方法之后再往后就是跟之前的方法类似了,之前的方法里面也都差不多一样,感觉像是拖动的时候会触发这个onBindViewHolder,但是里面具体是怎么实际的计算的,还不清楚。

3.第三条调用时机

还是回到之前的说的onTouchEvent里面,拖动的状态下,

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

    if (mScrollState == SCROLL_STATE_DRAGGING) {
        mReusableIntPair[0] = 0;
        mReusableIntPair[1] = 0;
        if (dispatchNestedPreScroll(
            canScrollHorizontally ? dx : 0,
            canScrollVertically ? dy : 0,
            mReusableIntPair, mScrollOffset, TYPE_TOUCH
            )) {
            dx -= mReusableIntPair[0];
            dy -= mReusableIntPair[1];
            // Updated the nested offsets
            mNestedOffsets[0] += mScrollOffset[0];
            mNestedOffsets[1] += mScrollOffset[1];
            // Scroll has initiated, prevent parents from intercepting
            getParent().requestDisallowInterceptTouchEvent(true);
        }

        mLastTouchX = x - mScrollOffset[0];
        mLastTouchY = y - mScrollOffset[1];

        if (scrollByInternal(
            canScrollHorizontally ? dx : 0,
            canScrollVertically ? dy : 0,
            e, TYPE_TOUCH)) {
            getParent().requestDisallowInterceptTouchEvent(true);
        }
        if (mGapWorker != null && (dx != 0 || dy != 0)) {
            mGapWorker.postFromTraversal(this, dx, dy);
        }
    }
}
break;

这里面有个mGapWorker,这个mGapWorker是干啥的?可以大概看下里面的东西,

final class GapWorker implements Runnable

这么着就比较容易理解了,他是个runnable业务的封装,那就基本上可以只看里面的run方法了,因为run方法里面应该是比较核心的内容,

@Override
public void run() {
    try {
        TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);

        if (mRecyclerViews.isEmpty()) {
        // abort - no work to do
    	return;
    }

    // Query most recent vsync so we can predict next one. Note that drawing time not yet
    // valid in animation/input callbacks, so query it here to be safe.
    //这里会遍历一个recyclerView的集合,拿到最新的一帧的毫秒时间差,
    final int size = mRecyclerViews.size();
    long latestFrameVsyncMs = 0;
    for (int i = 0; i < size; i++) {
        RecyclerView view = mRecyclerViews.get(i);
        if (view.getWindowVisibility() == View.VISIBLE) {
        	latestFrameVsyncMs = Math.max(view.getDrawingTime(), latestFrameVsyncMs);
        }
    }

	//如果本身就是最新的不做处理,直接返回
    if (latestFrameVsyncMs == 0) {
        // abort - either no views visible, or couldn't get last vsync for estimating next
        return;
    }

	//这个是拿下一次的时间值,他会添加一个时间间隔,这个间隔是根据帧率算出来的,
    long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;

    prefetch(nextFrameNs);

    // TODO: consider rescheduling self, if there's more work to do
    } finally {
        mPostTimeNs = 0;
        TraceCompat.endSection();
    }
}

里面说的帧率间隔是相加出来的,mFrameIntervalNs他的赋值的代码在这里,

float refreshRate = 60.0f;
mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate);

这是截取的代码都在RecyclerView里面能找到,那这么着解释的话,就是说这个其实是一个定时检测的机制,因为到下一帧的时候他才会走到prefetch(nextFrameNs)这个方法里面,注意那个单位是纳秒级别的,所以说这个其实还是1s60帧,所以说结果应该是一帧走了多少纳秒,所以就感觉源码里面计算量级还是很强的。

prefetch(nextFrameNs)的方法内容如下:

void prefetch(long deadlineNs) {
    buildTaskList();
    flushTasksWithDeadline(deadlineNs);
}

再接着往里面走,

private void flushTasksWithDeadline(long deadlineNs) {
    for (int i = 0; i < mTasks.size(); i++) {
        final Task task = mTasks.get(i);
        if (task.view == null) {
            break; // done with populated tasks
        }
        flushTaskWithDeadline(task, deadlineNs);
        task.clear();
    }
}

这里面从从每个task把里面的东西取出来,大概的意思就是可以定义很多个task每个task都能和view关联起来吧?跟着这方法再往里面走,

private void flushTaskWithDeadline(Task task, long deadlineNs) {
    long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
    RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
    task.position, taskDeadlineNs);
    if (holder != null
    && holder.mNestedRecyclerView != null
    && holder.isBound()
    && !holder.isInvalid()) {
    	prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);
    }
}

这个大概的意思就是从holder里面反向取出来recyclerView,然后再接着调用,

private void prefetchInnerRecyclerViewWithDeadline(@Nullable RecyclerView innerView,
            long deadlineNs) {
        if (innerView == null) {
            return;
        }

        if (innerView.mDataSetHasChangedAfterLayout
                && innerView.mChildHelper.getUnfilteredChildCount() != 0) {
            // RecyclerView has new data, but old attached views. Clear everything, so that
            // we can prefetch without partially stale data.
            innerView.removeAndRecycleViews();
        }

        // do nested prefetch!
        final LayoutPrefetchRegistryImpl innerPrefetchRegistry = innerView.mPrefetchRegistry;
        innerPrefetchRegistry.collectPrefetchPositionsFromView(innerView, true);

        if (innerPrefetchRegistry.mCount != 0) {
            try {
                TraceCompat.beginSection(RecyclerView.TRACE_NESTED_PREFETCH_TAG);
                innerView.mState.prepareForNestedPrefetch(innerView.mAdapter);
                for (int i = 0; i < innerPrefetchRegistry.mCount * 2; i += 2) {
                    // Note that we ignore immediate flag for inner items because
                    // we have lower confidence they're needed next frame.
                    final int innerPosition = innerPrefetchRegistry.mPrefetchArray[i];
                    prefetchPositionWithDeadline(innerView, innerPosition, deadlineNs);
                }
            } finally {
                TraceCompat.endSection();
            }
        }
}

这里面会接着继续调用prefetchPositionWithDeadline(innerView, innerPosition, deadlineNs);

private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
            int position, long deadlineNs) {
        if (isPrefetchPositionAttached(view, position)) {
            // don't attempt to prefetch attached views
            return null;
        }

        RecyclerView.Recycler recycler = view.mRecycler;
        RecyclerView.ViewHolder holder;
        try {
            view.onEnterLayoutOrScroll();
            //调到这里的时候就又重新回到之前说的地方了,等于是从另外的入口调用的方法,后面东西就重复了
            holder = recycler.tryGetViewHolderForPositionByDeadline(
                    position, false, deadlineNs);

            if (holder != null) {
                if (holder.isBound() && !holder.isInvalid()) {
                    // Only give the view a chance to go into the cache if binding succeeded
                    // Note that we must use public method, since item may need cleanup
                    recycler.recycleView(holder.itemView);
                } else {
                    // Didn't bind, so we can't cache the view, but it will stay in the pool until
                    // next prefetch/traversal. If a View fails to bind, it means we didn't have
                    // enough time prior to the deadline (and won't for other instances of this
                    // type, during this GapWorker prefetch pass).
                    recycler.addViewHolderToRecycledViewPool(holder, false);
                }
            }
        } finally {
            view.onExitLayoutOrScroll(false);
        }
        return holder;
}

4.结论

所以其实recyclerView滑动的时候,

1.初始化的时候是会走到onBindViewHolder的。

2.触摸时onTouch也是会触发onBindViewHolder的,但是是有限制的,是根据计算得出来的。

3.onAttachedToWindow方法里面创建出来的GapWorker也是会走到onBindViewHolder的,这个东西是内置嵌套的

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在RecyclerView中显示Room数据库中的数据,您需要完成以下步骤: 1. 在您的应用中创建一个Room数据库,并定义一个实体类表示您要在RecyclerView中显示的数据。 2. 创建一个DAO(数据访问对象)类,用于执行数据访问操作(例如,查询所有数据)。 3. 在您的应用中编写一个Repository类,该类将调用DAO类中的方法来访问Room数据库中的数据。 4. 在您的应用中创建一个ViewModel类,该类将使用Repository类中的数据并将其提供给您的RecyclerView。 5. 在您的应用中创建一个RecyclerView适配器类,该类将使用ViewModel类中提供的数据来显示RecyclerView条目。 以下是一个示例代码片段,展示如何从Room数据库中检索数据并将其提供给RecyclerView: ``` // 在ViewModel类中获取数据 public class MyViewModel extends ViewModel { private MyRepository mRepository; private LiveData<List<MyEntity>> mData; public MyViewModel(Application application) { super(application); mRepository = new MyRepository(application); mData = mRepository.getAllData(); } public LiveData<List<MyEntity>> getData() { return mData; } } // 在Repository类中获取数据 public class MyRepository { private MyDao mDao; private LiveData<List<MyEntity>> mData; public MyRepository(Application application) { MyDatabase db = MyDatabase.getDatabase(application); mDao = db.myDao(); mData = mDao.getAllData(); } public LiveData<List<MyEntity>> getAllData() { return mData; } } // 在RecyclerView适配器类中使用数据 public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { private List<MyEntity> mData; public MyAdapter(List<MyEntity> data) { mData = data; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.my_item_layout, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder, int position) { MyEntity entity = mData.get(position); holder.mTextView.setText(entity.getText()); } @Override public int getItemCount() { return mData.size(); } public static class ViewHolder extends RecyclerView.ViewHolder { public TextView mTextView; public ViewHolder(View view) { super(view); mTextView = view.findViewById(R.id.my_text_view); } } } ``` 请注意,以上代码只是示例,并且需要根据您的应用程序的具体要求进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值