備注
雖然工作有幾年了,但是玩安卓的時(shí)間比較短,第一次看recycleview源碼看的也是有點(diǎn)頭大。這篇文章純單是自己的一個(gè)學(xué)習(xí)筆記看待,描述有問題的地方,敬請(qǐng)諒解。
原因
為什么會(huì)有想法去看RecycleView源碼,主要的原因是:剛好在工作中碰到一個(gè)問題是:在滑動(dòng)item中,未配置的imageview會(huì)顯示之前的配置的imageView。由于一開始對(duì)recycleview的不熟悉,這個(gè)問題弄了好久。因此,想著要不干脆一不做二不休,去理解梳理一遍源碼吧。
帶著問題
看源碼,經(jīng)??粗粗拖肜斫饷恳徊降木唧w的邏輯,但是這樣勢必造成理解起來很困難,花的時(shí)間也很長。還是需要帶著問題去看源碼,通過打斷點(diǎn)的方式,去查看調(diào)用方式,理清一個(gè)脈絡(luò),去慢慢的理解源碼。
那么接下來,我們就慢慢去查看源碼
路口
那么我們從哪里開始看代碼呢?我們?cè)谝婚_始設(shè)置adapter調(diào)用的函數(shù)是:setAdapter函數(shù),那么就從這里開始看起:
/**
* Set a new adapter to provide child views on demand.
* <p>
* When adapter is changed, all existing views are recycled back to the pool. If the pool has
* only one adapter, it will be cleared.
*/
public void setAdapter(@Nullable Adapter adapter) {
....
//設(shè)置新的adapter,并且觸發(fā)監(jiān)聽
setAdapterInternal(adapter, false, true);
processDataSetCompletelyChanged(false);
//請(qǐng)求布局,直接調(diào)用View類的請(qǐng)求布局方法
requestLayout();
}
setAdapter函數(shù)里,按我們所關(guān)心的具體做了兩件事情:
- 調(diào)用
setAdapterInternal函數(shù),用新的adapter替換掉老的adapter,并且觸發(fā)監(jiān)聽。看看setAdapterInternal具體做了什么事情:
/**
* Replaces the current adapter with the new one and triggers listeners.
* @param adapter The new adapter
* @param compatibleWithPrevious If true, the new adapter is using the same View Holders and
* item types with the current adapter (helps us avoid cache
* invalidation).
* @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If
* compatibleWithPrevious is false, this parameter is ignored.
*/
private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver); // 注銷老的adapter觀察者
mAdapter.onDetachedFromRecyclerView(this); //解綁recycleview
}
....
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver); //注冊(cè)觀察者
adapter.onAttachedToRecyclerView(this);
}
...
}
setAdapterInternal函數(shù)中主要做的是注銷之前注冊(cè)的adapter,重新注冊(cè)新的adapter,并且綁定recycleView。
- 調(diào)用
requestLayout函數(shù),請(qǐng)求布局。
我們看看requestLayout中函數(shù)的實(shí)現(xiàn):
@Override
public void requestLayout() {
if (mInterceptRequestLayoutDepth == 0 && !mLayoutSuppressed) {
super.requestLayout();
} else {
mLayoutWasDefered = true;
}
}
我們看到requestLayout 最終調(diào)用父類的requestLayout();但是,我現(xiàn)在對(duì)自定義的View不是很熟悉,按照參考的一些博客的說法是:最終調(diào)用到onLayout函數(shù),最終由子類自己實(shí)現(xiàn)onLayout函數(shù)實(shí)現(xiàn)。
@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;
}
從這個(gè)函數(shù)我們看到,最終是調(diào)用dispatchLayout()去分發(fā)Layout。
又可以繼續(xù)看看dispatchLayout函數(shù)具體做了啥:
void dispatchLayout() {
...
if (mState.mLayoutStep == State.STEP_START) {
//dispath第一步
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
//dispatch第二步
dispatchLayoutStep2();
}
...
//dispatch第二步
dispatchLayoutStep3();
}
2.1 dispatch 第一步
/**
* The first step of a layout where we;
* - process adapter updates
* - decide which animation should run
* - save information about current views
* - If necessary, run predictive layout and save its information
*/
private void dispatchLayoutStep1() {
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
startInterceptRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
if (mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder);
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
......
mState.mLayoutStep = State.STEP_LAYOUT;
}
按照dispatchLayoutStep1()函數(shù)說明執(zhí)行以下幾件事:
1.處理Adapter的更新
2.決定那些動(dòng)畫需要執(zhí)行
3.保存當(dāng)前View的信息
4.如果必要的話,執(zhí)行上一個(gè)Layout的操作并且保存他的信息
5.更新mLayoutStep 為State.STEP_LAYOUT
2.2 dispatch 第二步
/**
* The second layout step where we do the actual layout of the views for the final state.
* This step might be run multiple times if necessary (e.g. measure).
*/
private void dispatchLayoutStep2() {
......
//更新mItemCount
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
//運(yùn)行l(wèi)ayout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
......
}
朋友們,看到這一步,最重點(diǎn)的要來了。mLayout.onLayoutChildren這個(gè)函數(shù)就是我們要開始規(guī)劃item怎么規(guī)劃,怎么布局的關(guān)鍵啦。這里我們放在后面介紹,這里的內(nèi)容會(huì)比較多。
2.3 dispatch 第三步
The final step of the layout where we save the information about views for animations,
* trigger animations and do any necessary cleanup.
第三步按照函數(shù)的說明:主要做了保存一些views和animations信息,并且觸發(fā)animations并且做一些一些必要的清理。
看到這里我們大概算是理清了一個(gè)脈絡(luò):直接拿取另外一個(gè)博主的截圖:

重點(diǎn)來了
上面我們將一個(gè)流程梳理完成,從setAdapter到最終的
onLayoutChildren函數(shù)。接著我們看onLayoutChildren具體是怎么實(shí)現(xiàn)的。
在dispatchLayoutStep2函數(shù)調(diào)用的onLayoutChildren的方式是:mLayout.onLayoutChildren(mRecycler, mState); 。而mLayout 是什么時(shí)候賦值的呢?
我們一開始在setAdapter的時(shí)候,也會(huì)使用setLayoutManager函數(shù)設(shè)定mLayout。這次我們以LinearLayoutManager作為參考,進(jìn)行代碼解析。
因此onLayoutChildren 最終是調(diào)用mLayout對(duì)象的onLayoutChildren。這里也間接說明RecyclerView布局就通過這個(gè)mLayout布局管理者來做。
我們繼續(xù)看代碼:
public void onLayoutChildren(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 (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;
} else if (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) {
final int current;
final int 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;
final int 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;
final int 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
updateLayoutSpublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
//找尋錨點(diǎn)
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
//兩個(gè)方向填充,從錨點(diǎn)往上,從錨點(diǎn)往下
// 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
....
// resolve layout direction
//判斷繪制方向,給mShouldReverseLayout賦值,默認(rèn)是正向繪制,則mShouldReverseLayout是false
resolveShouldLayoutReverse();
final View focused = getFocusedChild();
//mValid的默認(rèn)值是false,一次測量之后設(shè)為true,onLayout完成后會(huì)回調(diào)執(zhí)行reset方法,又變?yōu)閒alse
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
|| mPendingSavedState != null) {
....
//mStackFromEnd默認(rèn)是false,除非手動(dòng)調(diào)用setStackFromEnd()方法,兩個(gè)都會(huì)false,異或則為false
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
//計(jì)算錨點(diǎn)的位置和偏移量
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
....
} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
>= mOrientationHelper.getEndAfterPadding()
|| mOrientationHelper.getDecoratedEnd(focused)
<= mOrientationHelper.getStartAfterPadding())) {
....
}
....
//mLayoutFromEnd為false
if (mAnchorInfo.mLayoutFromEnd) {
//倒著繪制的話,先往上繪制,再往下繪制
// fill towards start
// 從錨點(diǎn)到往上
updateLayoutStateToFillStart(mAnchorInfo);
....
fill(recycler, mLayoutState, state, false);
....
// 從錨點(diǎn)到往下
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
....
//調(diào)兩遍fill方法
fill(recycler, mLayoutState, state, false);
....
if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
....
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
....
}
} else {
//正常繪制流程的話,先往下繪制,再往上繪制
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
....
fill(recycler, mLayoutState, state, false);
....
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
....
fill(recycler, mLayoutState, state, false);
....
if (mLayoutState.mAvailable > 0) {
....
// start could not consume all it should. add more items towards end
updateLayoutStateToFillEnd(lastElement, endOffset);
....
fill(recycler, mLayoutState, state, false);
....
}
}
....
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
//完成后重置參數(shù)
if (!state.isPreLayout()) {
mOrientationHelper.onLayoutComplete();
} else {
mAnchorInfo.reset();
}
mLastStackFromEnd = mStackFromEnd;
}
這里我們大概概括一下onLayoutChildren中布局的邏輯:
- 通過
resolveShouldLayoutReverse判斷繪制的方向 - 獲取錨點(diǎn)mAnchorInfo。
- 繪制的方式有兩種:
3.1 以錨點(diǎn)為基準(zhǔn),先往上繪制,再往下繪制。
3.2 以錨點(diǎn)為基準(zhǔn),先往下繪制,再往上繪制。 - 繪制完后還有剩余空間可以繪制,繼續(xù)繪制。
接下來,我們基于以上幾個(gè)點(diǎn)繼續(xù)看源碼:
繪制方向
// resolve layout direction
resolveShouldLayoutReverse();
/**
* Calculates the view layout order. (e.g. from end to start or start to end)
* RTL layout support is applied automatically. So if layout is RTL and
* {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
*/
private void resolveShouldLayoutReverse() {
// A == B is the same result, but we rather keep it readable
if (mOrientation == VERTICAL || !isLayoutRTL()) {
mShouldReverseLayout = mReverseLayout;
} else {
mShouldReverseLayout = !mReverseLayout;
}
}
在onLayoutChildren函數(shù)中調(diào)用resolveShouldLayoutReverse函數(shù)根據(jù)mOrientation 變量判斷是橫向填充還是豎向填充。mOrientation 默認(rèn)是為VERTICAL,調(diào)用setOrientation設(shè)置mOrientation。
尋找錨點(diǎn)
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
mAnchorInfo就是我們要找的錨點(diǎn),它的成員變量主要有以下四個(gè)
int mPosition; 位置
int mCoordinate; 坐標(biāo)
boolean mLayoutFromEnd;
boolean mValid;
mValid 默認(rèn)值為false,一次測量之后設(shè)為true,onLayout完成后會(huì)回調(diào)執(zhí)行reset方法,又變?yōu)閒alse。
mLayoutFromEnd mShouldReverseLayout默認(rèn)是fasle的,mStackFromEnd默認(rèn)是false,除非手動(dòng)調(diào)用setStackFromEnd()方法,兩個(gè)都會(huì)false,異或則為false。
updateAnchorInfoForLayout這個(gè)函數(shù)是更新mAnchorInfo 數(shù)據(jù)中的mPosition和mCoordinate,后續(xù)再分析。
開始繪制
繪制的地方,主要就是兩種方向,正向(先向上再向下),逆向(先向下再向上),所以這里我們就看平常的情況。
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
......
fill(recycler, mLayoutState, state, false);
......
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
......
fill(recycler, mLayoutState, state, false);
......
if (mLayoutState.mAvailable > 0) {
......
updateLayoutStateToFillEnd(lastElement, endOffset);
mLayoutState.mExtraFillSpace = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
這里我們發(fā)現(xiàn)主要通過updateLayoutStateToFillEnd,updateLayoutStateToFillStart,fill函數(shù)進(jìn)行繪制。
updateLayoutStateToFill...其實(shí)就是確定當(dāng)前方向上錨點(diǎn)的相關(guān)的狀態(tài)信息。
fill()主要用來繪制可以看到這里至少調(diào)用了兩次fill()方法,當(dāng)還有剩余可以繪制的時(shí)候會(huì)再調(diào)一次fill()方法。
這里繼續(xù)引用一個(gè)博主的圖:

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
.....
layoutChunk(recycler, state, layoutState, layoutChunkResult);
.....
return start - layoutState.mAvailable;
}
fill函數(shù)最終調(diào)用layoutChunk函數(shù)。
View view = layoutState.next(recycler);
.....
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(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);
....
這個(gè)函數(shù)關(guān)注點(diǎn)會(huì)很多,我一個(gè)個(gè)的看:
- layoutState.next(recycler);
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
按照函數(shù)說明就是用來獲取view的,那么怎么獲取呢?
mScrapList 這個(gè)變量默認(rèn)是null,只有執(zhí)行l(wèi)ayoutForPredictiveAnimations前不為空,執(zhí)行完后又變?yōu)榭眨赃@里暫時(shí)不需要考慮。
getViewForPosition 涉及的東西比較多,流程上簡單的看一下View view = recycler.getViewForPosition(mCurrentPosition);
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
/**
* Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
* cache, the RecycledViewPool, or creating it directly.
*/
/**
* 注釋寫的很清楚,從Recycler的scrap,cache,RecyclerViewPool,或者直接create創(chuàng)建
*/
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
//一堆判斷之后,如果不成立
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
可以看到這里,getViewForPosition會(huì)調(diào)用tryGetViewHolderForPositionByDeadline方法,tryGetViewHolderForPositionByDeadline方法的注釋寫的很清楚從Recycler的scrap,cache,RecyclerViewPool,或者直接create創(chuàng)建。
- addView
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);
}
}
剩下的就是RecyclerView的addView方法。添加完View后會(huì)調(diào)用
//測量ChildView
measureChildWithMargins(view, 0, 0);
//----------------------------------------------------------
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//設(shè)置分割線中的回調(diào)方法
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight()
+ lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom()
+ lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
//子View的測量
child.measure(widthSpec, heightSpec);
}
}
從這個(gè)方法里我們看到了子View的測量,當(dāng)然還有一個(gè)需要我們注意的地方那就是mRecyclerView.getItemDecorInsetsForChild(child)
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}
if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
// changed/invalid items should not be updated until they are rebound.
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
//getItemOffsets()實(shí)現(xiàn)分割線的回調(diào)方法!
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
其實(shí)可以看到這里在測量子View的時(shí)候是將我們實(shí)現(xiàn)自定義分割線重寫的getItemOffsets方法。這里其實(shí)也就可以理解了自定義分割線的原理就是在子View的測量過程前給上下左右加上自定義分割線所對(duì)應(yīng)設(shè)置給這個(gè)child的邊距。
測量完成后,緊接著就調(diào)用了layoutDecoratedWithMargins(view, left, top, right, bottom)對(duì)子View完成了layout。

public void layoutDecoratedWithMargins(View child, int left, int top, int right,
int bottom) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = lp.mDecorInsets;
//layout
child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
right - insets.right - lp.rightMargin,
bottom - insets.bottom - lp.bottomMargin);
}
最后
中間有大量摘錄
博客:http://www.itdecent.cn/p/c52b947fe064
博客:http://www.itdecent.cn/p/10298503c134
這篇文章全當(dāng)自己的學(xué)習(xí)筆記。