if (mLayout == null) { defaultOnMeasure(widthSpec, heightSpec); return; }
这里调用了defaultOnMeasure方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/** * An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios * where this RecyclerView is otherwise lacking better information. */ voiddefaultOnMeasure(int widthSpec, int heightSpec){ // calling LayoutManager here is not pretty but that API is already public and it is better // than creating another method since this is internal. finalint width = LayoutManager.chooseSize(widthSpec, getPaddingLeft() + getPaddingRight(), ViewCompat.getMinimumWidth(this)); finalint height = LayoutManager.chooseSize(heightSpec, getPaddingTop() + getPaddingBottom(), ViewCompat.getMinimumHeight(this));
/** * Chooses a size from the given specs and parameters that is closest to the desired size * and also complies with the spec. * * @param spec The measureSpec * @param desired The preferred measurement * @param min The minimum value * * @return A size that fits to the given specs */ publicstaticintchooseSize(int spec, int desired, int min){ finalint mode = View.MeasureSpec.getMode(spec); finalint size = View.MeasureSpec.getSize(spec); switch (mode) { case View.MeasureSpec.EXACTLY: return size; case View.MeasureSpec.AT_MOST: return Math.min(size, Math.max(desired, min)); case View.MeasureSpec.UNSPECIFIED: default: return Math.max(desired, min); } }
//调用LayoutManager.onMeasure方法测量 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); //判断是否可以跳过测量或者adapter为null,直接返回。 /*自定义 view 设置的宽高参数不是 MeasureSpec.EXACTLY 的话,对于子 View 是 match_parent 需要额外处理。*/ finalboolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; if (measureSpecModeIsExactly || mAdapter == null) { return; } //开始测量 if (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); } // set dimensions in 2nd step. Pre-layout should happen with old dimensions for // consistency mLayout.setMeasureSpecs(widthSpec, heightSpec); mState.mIsMeasuring = true; dispatchLayoutStep2();
// now we can get the width and height from the children. mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// if RecyclerView has non-exact width and height and if there is at least one child // which also has non-exact width & height, we have to re-measure. //第二次 if (mLayout.shouldMeasureTwice()) { mLayout.setMeasureSpecs( MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); mState.mIsMeasuring = true; dispatchLayoutStep2(); // now we can get the width and height from the children. mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); } }
//是否要运行动画 if (mState.mRunSimpleAnimations) { // Step 0: Find out where all non-removed items are, pre-layout // 找出所有未移除的ItemView,进行预布局 ...... } if (mState.mRunPredictiveAnimations) { // Step 1: run prelayout: This will use the old positions of items. The layout manager // is expected to layout everything, even removed items (though not to add removed // items back to the container). This gives the pre-layout position of APPEARING views // which come into existence as part of the real layout.
// Save old positions so that LayoutManager can run its mapping logic. //保存旧状态进行预布局。 ...... } else { clearOldPositions(); } onExitLayoutOrScroll(); stopInterceptRequestLayout(false); //改变状态 mState.mLayoutStep = State.STEP_LAYOUT; }
privatevoidresolveShouldLayoutReverse(){ // A == B is the same result, but we rather keep it readable //默认情况下,`mReverseLayout`为false,是不会倒着绘制的。手动调用`setReverseLayout`方法可以改变`mRverseLayout`的值。
else { if (mHasFixedSize@Override publicvoidonLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state){ // layout algorithm: // 1) by checking children and other variables, find an anchor coordinate and an anchor // item position. // 2) fill towards start, stacking from bottom // 3) fill towards end, stacking from top // 4) scroll to fulfill requirements like stack from bottom. // create layout state if (DEBUG) { Log.d(TAG, "is pre layout:" + state.isPreLayout()); } if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) { if (state.getItemCount() == 0) { removeAndRecycleAllViews(recycler); return; } } if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { mPendingScrollPosition = mPendingSavedState.mAnchorPosition; }
ensureLayoutState(); mLayoutState.mRecycle = false; // resolve layout direction resolveShouldLayoutReverse();
final View focused = getFocusedChild(); if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION || mPendingSavedState != null) { mAnchorInfo.reset(); mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; // calculate anchor position and coordinate updateAnchorInfoForLayout(recycler, state, mAnchorInfo); mAnchorInfo.mValid = true; } elseif (focused != null && (mOrientationHelper.getDecoratedStart(focused) >= mOrientationHelper.getEndAfterPadding() || mOrientationHelper.getDecoratedEnd(focused) <= mOrientationHelper.getStartAfterPadding())) { // This case relates to when the anchor child is the focused view and due to layout // shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows // up after tapping an EditText which shrinks RV causing the focused view (The tapped // EditText which is the anchor child) to get kicked out of the screen. Will update the // anchor coordinate in order to make sure that the focused view is laid out. Otherwise, // the available space in layoutState will be calculated as negative preventing the // focused view from being laid out in fill. // Note that we won't update the anchor position between layout passes (refer to // TestResizingRelayoutWithAutoMeasure), which happens if we were to call // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference // child which can change between layout passes). mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused)); } if (DEBUG) { Log.d(TAG, "Anchor info:" + mAnchorInfo); }
// LLM may decide to layout items for "extra" pixels to account for scrolling target, // caching or predictive animations.
mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; mReusableIntPair[0] = 0; mReusableIntPair[1] = 0; calculateExtraLayoutSpace(state, mReusableIntPair); int extraForStart = Math.max(0, mReusableIntPair[0]) + mOrientationHelper.getStartAfterPadding(); int extraForEnd = Math.max(0, mReusableIntPair[1]) + mOrientationHelper.getEndPadding(); if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION && mPendingScrollPositionOffset != INVALID_OFFSET) { // if the child is visible and we are going to move it around, we should layout // extra items in the opposite direction to make sure new items animate nicely // instead of just fading in final View existing = findViewByPosition(mPendingScrollPosition); if (existing != null) { finalint current; finalint upcomingOffset; if (mShouldReverseLayout) { current = mOrientationHelper.getEndAfterPadding() - mOrientationHelper.getDecoratedEnd(existing); upcomingOffset = current - mPendingScrollPositionOffset; } else { current = mOrientationHelper.getDecoratedStart(existing) - mOrientationHelper.getStartAfterPadding(); upcomingOffset = mPendingScrollPositionOffset - current; } if (upcomingOffset > 0) { extraForStart += upcomingOffset; } else { extraForEnd -= upcomingOffset; } } } int startOffset; int endOffset; finalint firstLayoutDirection; if (mAnchorInfo.mLayoutFromEnd) { firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL : LayoutState.ITEM_DIRECTION_HEAD; } else { firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD : LayoutState.ITEM_DIRECTION_TAIL; }
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); detachAndScrapAttachedViews(recycler); mLayoutState.mInfinite = resolveIsInfinite(); mLayoutState.mIsPreLayout = state.isPreLayout(); // noRecycleSpace not needed: recycling doesn't happen in below's fill // invocations because mScrollingOffset is set to SCROLLING_OFFSET_NaN mLayoutState.mNoRecycleSpace = 0; if (mAnchorInfo.mLayoutFromEnd) { // fill towards start updateLayoutStateToFillStart(mAnchorInfo); mLayoutState.mExtraFillSpace = extraForStart; fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; finalint firstElement = mLayoutState.mCurrentPosition; if (mLayoutState.mAvailable > 0) { extraForEnd += mLayoutState.mAvailable; } // fill towards end updateLayoutStateToFillEnd(mAnchorInfo); mLayoutState.mExtraFillSpace = extraForEnd; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) { // end could not consume all. add more items towards start extraForStart = mLayoutState.mAvailable; updateLayoutStateToFillStart(firstElement, startOffset); mLayoutState.mExtraFillSpace = extraForStart; fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; } } else { // fill towards end updateLayoutStateToFillEnd(mAnchorInfo); mLayoutState.mExtraFillSpace = extraForEnd; fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; finalint lastElement = mLayoutState.mCurrentPosition; if (mLayoutState.mAvailable > 0) { extraForStart += mLayoutState.mAvailable; } // fill towards start updateLayoutStateToFillStart(mAnchorInfo); mLayoutState.mExtraFillSpace = extraForStart; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) { extraForEnd = mLayoutState.mAvailable; // start could not consume all it should. add more items towards end updateLayoutStateToFillEnd(lastElement, endOffset); mLayoutState.mExtraFillSpace = extraForEnd; fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; } }
// changes may cause gaps on the UI, try to fix them. // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have // changed if (getChildCount() > 0) { // because layout from end may be changed by scroll to position // we re-calculate it. // find which side we should check for gaps. if (mShouldReverseLayout ^ mStackFromEnd) { int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true); startOffset += fixOffset; endOffset += fixOffset; fixOffset = fixLayoutStartGap(startOffset, recycler, state, false); startOffset += fixOffset; endOffset += fixOffset; } else { int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true); startOffset += fixOffset; endOffset += fixOffset; fixOffset = fixLayoutEndGap(endOffset, recycler, state, false); startOffset += fixOffset; endOffset += fixOffset; } } layoutForPredictiveAnimations(recycler, state, startOffset, endOffset); if (!state.isPreLayout()) { mOrientationHelper.onLayoutComplete(); } else { mAnchorInfo.reset(); } mLastStackFromEnd = mStackFromEnd; if (DEBUG) { validateChildOrder(); } }) { mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); return; } // custom onMeasure if (mAdapterUpdateDuringMeasure) { startInterceptRequestLayout(); onEnterLayoutOrScroll(); processAdapterUpdatesAndSetAnimationFlags(); onExitLayoutOrScroll();
if (mState.mRunPredictiveAnimations) { mState.mInPreLayout = true; } else { // consume remaining updates to provide a consistent state with the layout pass. mAdapterHelper.consumeUpdatesInOnePass(); mState.mInPreLayout = false; } mAdapterUpdateDuringMeasure = false; stopInterceptRequestLayout(false); } elseif (mState.mRunPredictiveAnimations) { // If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true: // this means there is already an onMeasure() call performed to handle the pending // adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout // with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time // because getViewForPosition() will crash when LM uses a child to measure. setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight()); return; }
@Override protectedvoidonLayout(boolean changed, int l, int t, int r, int b){ TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout();// 进行layout操作 TraceCompat.endSection(); mFirstLayoutComplete = true;//赋值为true,即已经layout一次了 }
voiddispatchLayout(){ if (mAdapter == null) { Log.e(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 (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); mLayout.setExactMeasureSpecsFrom(this); dispatchLayoutStep2(); } elseif (mAdapterHelper.hasUpdates() || 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. mLayout.setExactMeasureSpecsFrom(this); dispatchLayoutStep2(); } else { // always make sure we sync them (to ensure mode is exact) mLayout.setExactMeasureSpecsFrom(this); } dispatchLayoutStep3(); }
publicvoiddetachAndScrapAttachedViews(@NonNull Recycler recycler){ finalint childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { final View v = getChildAt(i); scrapOrRecycleView(recycler, i, v); } }
遍历所有view,分离所有已经添加到RecyclerView的itemView。
1 2 3 4 5 6 7 8 9 10 11 12
privatevoidscrapOrRecycleView(Recycler recycler, int index, View view){ final ViewHolder viewHolder = getChildViewHolderInt(view); if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index);//移除VIew recycler.recycleViewHolderInternal(viewHolder);//缓存到CacheView或者RecycledViewPool中 } else { detachViewAt(index);//分离View recycler.scrapView(view);//scrap缓存 mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); } }
detachViewAt()方法分离视图,再通过scrapView()缓存到scrap中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
voidscrapView(View view){ final ViewHolder holder = getChildViewHolderInt(view); if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { holder.setScrapContainer(this, false); mAttachedScrap.add(holder);//保存到mAttachedScrap中 } else { if (mChangedScrap == null) { mChangedScrap = new ArrayList<ViewHolder>(); } holder.setScrapContainer(this, true); mChangedScrap.add(holder);//保存到mChangedScrap中 } }