先來看一段代碼
recycler_view.layoutManager =
LinearLayoutManager(
this,x
LinearLayoutManager.VERTICAL,
false
)
recycler_view.adapter = MyAdapter(this)
一個(gè)很普通的 recyclerView 的使用片段,這篇文章主要介紹一下 RecyclerView 的測量,回收,復(fù)用這會(huì)引申出來兩個(gè)類
LayoutManager:
負(fù)責(zé) measurelayout;主要有 LinearLayoutManager,GridLawyoutManager,StaggeredGridLayoutManager
Recyler :
四級(jí)回收,復(fù)用機(jī)制; 主要有 mAttachedScrap和mChangedScrap,mCacheViews,mViewCahceExtensions,mRecylerPool
這里也順便介紹一下 RecyclerView 相關(guān)的類吧
SmoothScroller : 滑動(dòng)速度控制;LinearSmoothScroller
SnapHelper : 慣性滑動(dòng)控制; LinearSnapHelper;
ItemDecoration: Item樣式裝飾; DividerItemDecoration,ItemTouchHelper
OnItemTouchListener: 手勢攔截器
DiffUtil : 差分異刷新;
嗯....類介紹完了,看看代碼吧,開始指定是 setLayoutManager 跑不了的,點(diǎn)擊進(jìn)去看看,
RecyclerView.java
public void setLayoutManager(@Nullable LayoutManager layout) {
if (layout == mLayout) {
return;
}
stopScroll();
// TODO We should do this switch a dispatchLayout pass and animate children. There is a good
// chance that LayoutManagers will re-use views.
if (mLayout != null) {
// end all running animations
```
} else {
mRecycler.clear();
}
// this is just a defensive measure for faulty item animators.
mChildHelper.removeAllViewsUnfiltered();
mLayout = layout;
if (layout != null) {
```
}
mRecycler.updateViewCacheSize();
requestLayout();
}
首先判斷了一下是否是同一個(gè) LayoutManager,然后停止?jié)L動(dòng),接著判斷本類的 mLayout 是否為空,這里是為了判斷 RecyclerView 之前是否已經(jīng)關(guān)聯(lián)了一個(gè) LayoutManager,如果之前關(guān)聯(lián)了,就需要做一些清除和解除關(guān)聯(lián)的操作.
然后判斷了一下傳遞進(jìn)來的 layout,這里是為了判斷這個(gè) LayoutManager 是否已經(jīng)和其他的 RecyclerView 相關(guān)聯(lián)了.
最后呢調(diào)用了 requestLayout() 因?yàn)?RecyclerView 也是一個(gè) ViewGroup,所以接下來也就進(jìn)入了它的 onMeasure 和 onLayout 方法.
來吧,看看 onMeasure(int widthSpec, int heightSpec)
RecyclerView.java
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
/**
* This specific call should be considered deprecated and replaced with
* {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
* break existing third party code but all documentation directs developers to not
* override {@link LayoutManager#onMeasure(int, int)} when
* {@link LayoutManager#isAutoMeasureEnabled()} returns true.
*/
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
final boolean 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);
}
} else {
...
}
}
這里面同樣判斷了一下 RecyclerView 是否與 LayoutManager 相關(guān)聯(lián),如果沒有關(guān)聯(lián),列表上的 item 是無法測量的,recyclerView 的寬高也就無法測量.這時(shí)會(huì)調(diào)用 defaultOnMeasure() 方法來給 recyclerView 設(shè)置一個(gè)盡量合理的寬高值,這個(gè)方法會(huì)跟據(jù) RecyclerView 父容器的寬高值,以及 recyclerView的寬高模式,它的 paddingLecft ,paddingRight MininumWidth 等值來計(jì)算一個(gè)盡量合理的寬高值,設(shè)置給 recyclerView.
接著往下看,判斷了是否自動(dòng)測量(就是 LayoutMananger 是否接管了對(duì)列表上 itme 的測量工作,官方也是建議在自定義 layoutMananger 的時(shí)候開啟這個(gè)自動(dòng)測量模式)
那么,為什么要開啟這個(gè)自動(dòng)測量模式?
開啟之后,在下面的這個(gè)if 分支里面,它能夠保證 RecyclerView 的寬高在不確定的情況下和列表中的寬高不確定的情況下,也能測量出一個(gè)正確的值,對(duì)于一個(gè) viewGroup 一般都會(huì)在 onMeasure 中遍歷它的子 view,挨個(gè)去測量,而在 RecyclerView 這里并沒有去遍歷測量,而是把 veiw 的測量交給了 LayoutManager,注意,這里只是 LayoutManager 接管了列表中子 view 的測量,recyclerView 本身的寬高如果在布局中沒有指定確定的值,那還是需要在這個(gè)方法中測量才能得到.
我們看到它調(diào)用了 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
看名字好像是 LayoutManager 開啟了對(duì)列表的測量工作,跟進(jìn)看一下
RecyclerView.Java
public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec,
int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
他調(diào)用了上面提到過的 defaultOnMeasure 方法,此時(shí)并沒有開啟對(duì)列表上 item 的測量工作,為什么要這么設(shè)計(jì)?因?yàn)楸M管 recyclerView 開啟了自動(dòng)測量,還是要測出recyclerView 兜底的寬高.
接下來會(huì)判斷 measureSpecModeIsExactly ,也就是判斷 recyclerView 的寬高是否都有一個(gè)確切的值,如果為 true,這直接返回.而如果沒有給指定確切的寬高值,就執(zhí)行下面的代碼,來測量 recyclerView的寬高.
接下來需要看的是關(guān)于 State 的這個(gè)判斷
if (mState.mLayoutStep == State.STEP_START)
這是 RecyclerView 的一個(gè)內(nèi)部類,
這個(gè)對(duì)象呢是在 RecyclerView 創(chuàng)建的時(shí)候創(chuàng)建的,它保存了 recyclerView 在當(dāng)前滑動(dòng)狀態(tài)下的所有信息,這里介紹一下 mLayoutStep 這字段
RecyclerView.State.Java
public static class State {
static final int STEP_START = 1;
static final int STEP_LAYOUT = 1 << 1;
static final int STEP_ANIMATIONS = 1 << 2;
```
int mLayoutStep = STEP_START;
```
}
實(shí)際上 LayoutMananger 把一次新的布局分成了三個(gè)階段 STEP_START, STEP_LAYOUT, STEP_ANIMATIONS
第一個(gè)階段,預(yù)布局階段,LayoutStep = STEP_START ,也是默認(rèn)值,對(duì)應(yīng)的方法為 dispatchLayoutStep1
第二個(gè)階段是真正開始布局的地方,LayoutStep = STEP_LAYOUT, 對(duì)應(yīng)的方法為 dispatchLayoutStep2
第三個(gè)階段是動(dòng)畫階段 LayoutStep = STEP_ANIMATIONS ,對(duì)應(yīng)的方法為 dispatchLayoutStep3
而上面的 onMeasure 方法里面也能看到確實(shí)是調(diào)用了1和2 方法,而
dispatchLayoutStep1 主要是開啟一次新的布局之前,收集需要做動(dòng)畫的item和他們對(duì)應(yīng)的動(dòng)畫信息,并且把LayoutStep 設(shè)置成 STEP_LAYOUT.
dispatchLayoutStep2 看一下注釋
/**
* 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).
*/
第二個(gè)布局步驟,我們對(duì)最終狀態(tài)的視圖進(jìn)行實(shí)際布局。如有必要,可多次執(zhí)行此步驟(例如測量)。
回到 onMeasure 第一次調(diào)用 dispatchLayoutStep2 的地方,這里讓 layoutManager 開始測量并布局列表上的 item,從而期望能夠計(jì)算出 recyclerView 的寬高.此時(shí)是期望,并不一定能準(zhǔn)確的測出,因?yàn)橄旅孢€有一種情況, mLayout.shouldMeasureTwice() 為true的時(shí)候還會(huì)再測量一遍,
// 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()) {
翻譯一下: 如果 RecyclerView 沒有確切的寬高,與此同時(shí)列表上至少存在一個(gè) item也沒有確切的寬和高,就會(huì)進(jìn)行第二次的測量來計(jì)算確切的寬高值比如:把 recyclerView 的寬和高設(shè)置成 wrap_content,item 設(shè)置成 match_parent 這時(shí)候當(dāng)?shù)谝淮握{(diào)用 dispatchLayoutStep2 的時(shí)候還不知道 RecyclerView的寬和高 就會(huì)執(zhí)行第二次測量,如果 item 有固定的寬高就不需要了.這里也能想到使用 RecylerView 的時(shí)候盡可能的指定確定的寬高來進(jìn)行優(yōu)化一下.
dispatchLayoutStep2 里面還有個(gè)重要的方法
private void dispatchLayoutStep2() {
...
mLayout.onLayoutChildren(mRecycler, mState);
...
}
從這里可以看出列表上的 item 具體怎么擺放,交由了 LayoutMananger 負(fù)責(zé),跟進(jìn)一下.
public void onLayoutChildren(Recycler recycler, State state) {
Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
發(fā)現(xiàn)有一個(gè) Log,自定義 LayoutManager ,你必須復(fù)寫這個(gè)方法.我們就以 LinearLayoutMananger 來看看具體情況.
LinearLayoutMananger .java
@Override
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 (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;
} 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();
...
}
首先判斷了 mPendingSavedState 是否為空,顧名思義,保存狀態(tài)的對(duì)象,如果不為空,就說明 RecyclerView 執(zhí)行了 View的狀態(tài)保存方法,也就是 onSaveInstanceState() ,這就說明了 RecyclerView 所在的頁面已經(jīng)不可見了,就需要把之前緩存的 ViewHolder 全部移除,并把 View 回收掉
來看下一個(gè)執(zhí)行的方法,ensureLayoutState();
private LayoutState mLayoutState;
....
void ensureLayoutState() {
if (mLayoutState == null) {
mLayoutState = createLayoutState();
}
}
這里就是去創(chuàng)建了 LayoutState 對(duì)象.這個(gè)對(duì)象里面存了些滑動(dòng)時(shí)的狀態(tài)信息,比如還有多少空間用來擺放列表的子 View mAvailable,當(dāng)前已經(jīng)擺放到了第幾個(gè)item mCurrentPosition,當(dāng)前滑動(dòng)的偏移量 mScrollingOffset.
接下來調(diào)用了 resolveShouldLayoutReverse(); 這個(gè)方法的意思是是否需要反轉(zhuǎn)布局
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í)際上布局是從上往下還是從下往上,是由下面的
mShouldReverseLayout ^ mStackFromEnd
決定的,正常情況下布局都是從上往下布局,如果調(diào)用了 setReverseLayout 就會(huì)從屏幕下方逐一往屏幕上方進(jìn)行布局,而如果調(diào)用了 setStackFromEnd ,列表的布局還是從上往下布局,但是等布局完成之后,就會(huì)自動(dòng)滾動(dòng)到列表的最后一個(gè) Item 上去,可用于即時(shí)通訊的會(huì)話列表.
這里還有個(gè)東西需要說一下 ,AnchorInfo mAnchorInfo,這個(gè)對(duì)象是在 RecyclerView 創(chuàng)建的時(shí)候就被創(chuàng)建了,它里面存儲(chǔ)了 RecyclerView 錨點(diǎn) view 的 position 信息和錨點(diǎn) view 在屏幕上的坐標(biāo),所以下面也就會(huì)調(diào)用 updateAnchorInfoForLayout 這個(gè)方法來收集錨點(diǎn) view 的position 和坐標(biāo)信息, updateAnchorInfoForLayout 會(huì)有三種更新方式,我們來看一下.
private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
AnchorInfo anchorInfo) {
if (updateAnchorFromPendingData(state, anchorInfo)) {
if (DEBUG) {
Log.d(TAG, "updated anchor info from pending information");
}
return;
}
if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
if (DEBUG) {
Log.d(TAG, "updated anchor info from existing children");
}
return;
}
if (DEBUG) {
Log.d(TAG, "deciding anchor info for fresh state");
}
anchorInfo.assignCoordinateFromPadding();
anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
}
1.如果之前頁面被回收了,本次頁面恢復(fù)之后,就會(huì)從 PendingData 中恢復(fù)上一次的布局狀態(tài),也就是從 PendingData 中恢復(fù) anchorInfo 的數(shù)據(jù)
2.從列表上找到一個(gè)具有焦點(diǎn)的 view 來更新anchorInfo 數(shù)據(jù)
3.根據(jù)布局的排列方式來取第一個(gè)或最后一個(gè)item來更新 anchorInfo數(shù)據(jù)
RecyclerView 出現(xiàn)列表加載完成之后自動(dòng)滾動(dòng)到某一個(gè) item上去,就是這個(gè)原因了.
至于這里為什么要在開始布局之前獲取 anchorInfo,是因?yàn)?LinearLayoutManager 在往列表上填充 item 的時(shí)候,并不是從上到下依次填充的,他是從錨點(diǎn)位置,從上往下填充給,接著繼續(xù)從錨點(diǎn)位置從下往上在填充一次,這是 LinearLayoutManager 往屏幕上填充時(shí)的一個(gè)策略.有興趣的可以瞅瞅.
回到 onLayoutChildren ,接著往下看,有這么一個(gè)注釋
// LLM may decide to layout items for "extra" pixels to account for scrolling target,
// caching or predictive animations.
也就是說下面這段代碼,就是為了解決我們在剛開始設(shè)置 LayoutMananger的時(shí)候,就調(diào)用了 scrollToPosition,這個(gè)時(shí)候在布局之前,就要去計(jì)算一下開始布局位置的一個(gè)偏移量.
再往下重點(diǎn)就來了
detachAndScrapAttachedViews(recycler)
這個(gè)方法的作用是,在一個(gè)新的布局之前需要對(duì)列表上存在的item對(duì)應(yīng)的viewHolder,把它們分門別類的回收一下,這里只是暫時(shí)性的把viewHolder回收到對(duì)應(yīng)的緩存集合里面,為了在接下來布局真正開始的時(shí)候能夠從 Recycler 這個(gè)回收池當(dāng)中能夠復(fù)用的到這個(gè) viewHolder,這里也就進(jìn)入了回收機(jī)制當(dāng)中.
來介紹一下 Rechcler 這個(gè)類吧,他是 RecyclerView 的內(nèi)部類,定義了 RecyclerView 的四級(jí)緩存
public final class Recycler{
//#1 不需要重新 bindViewHolder,
//這里存儲(chǔ)的viewHolder在復(fù)用的時(shí)候不需要從新調(diào)用 bindViewHolder 從新綁定數(shù)據(jù),RecyclerView 認(rèn)為這種情況的 viewHolder 還會(huì)重新出現(xiàn)在屏幕上,
//被這兩個(gè)集合緩存起來的 ViewHolder ,他們的狀態(tài)和數(shù)據(jù)是不會(huì)被重置的
ArrayList<ViewHolder> mAttachedScrap;
ArrayList<ViewHolder> mChangedScrap
//#2 可通過setItemCancheSize調(diào)整,默認(rèn)容量大小為2
//列表上下滑動(dòng),被滑出去的item 對(duì)應(yīng)的 ViewHolder 會(huì)被存放在這個(gè)集合里面,再次被滑進(jìn)屏幕的時(shí)候也是不需要重新綁定數(shù)據(jù)的
ArrayList<ViewHolder> mCachedViews;
//#3 自定義拓展View緩存
//這個(gè)屬于 RecyclerView 緩存能力的一種拓展,它允許開發(fā)者自定義 ViewHolder 的緩存位置和實(shí)現(xiàn)方式
ViewCacheExtension mViewCacheExtension;
//#4 根據(jù)veiwType存取ViewHolder,可通過setRecyclerViewPool調(diào)整,每個(gè)類型容量默認(rèn)為5
//當(dāng)切僅當(dāng)二級(jí)緩存 mCachedViews; 放不下的時(shí)候,才會(huì)把 ViewHolder 放入到這里面,
RecyclerViewPool mRecyclerPool;
}
當(dāng)調(diào)用 notifyItemChange() 刷新 RecyclerView 時(shí),我們指定的要刷新的 item 對(duì)應(yīng)的 ViewHolder 就會(huì)被放到 mChangeScrap 中,應(yīng)為 RecyclerView 發(fā)生了變化,而屏幕內(nèi)其他的item 沒有發(fā)生變化,被回收的時(shí)候就會(huì)被放到 mAttachScrap 里面.也就是說, mAttachScrap 里面保存的時(shí)原封不動(dòng)的 ViewHolder, mChangeScrap 保存的時(shí)發(fā)生變化的 ViewHolder.
而當(dāng)我們的item被劃出屏幕的時(shí)候,對(duì)應(yīng)的 ViewHolder 就會(huì)被緩存到 cachedViews 里面,但是 RecyclerView 認(rèn)為我們很有可能會(huì)再次反向滑動(dòng)列表,使得剛剛被滑出去的 item 又再次進(jìn)入屏幕,所以被緩存到 cachedViews 里面的 ViewHolder 被復(fù)用的時(shí)候,如果它和原來的位置一致,也就是不需要重新調(diào)用 bindViewHolder 來重新綁定數(shù)據(jù)的,但是這個(gè)集合有一個(gè)默認(rèn)容量為 2 的限制,當(dāng)我們的列表一直往下滑的時(shí)候,上半部分就會(huì)有越來越多的 item 被滑出屏幕,所以在一個(gè)新的 ViewHolder 被添加到 mCachedViews 緩存之前,他會(huì)校驗(yàn)容量是否已滿,如果已經(jīng)滿了,就會(huì)把最先添加到里面的 ViewHolder 給移除,放入到 recyclerPool 這個(gè)集合里,而 recyclerPool 在緩存階段是不參與的,只能在復(fù)用階段發(fā)揮作用.
上面我們介紹了 detachAndScrapAttachedViews(recycler); 這個(gè)方法,在布局前,將所有在顯示的HolderView從RecyclerView中剝離,但是為什么在開始一次新的布局之前要調(diào)用一次呢?
當(dāng)我們每次調(diào)用 notifyDataSetChanged() 和 notifyItemChanged() 都會(huì)觸發(fā) LayoutManager 的 onLayoutChildren 這個(gè)方法,在這個(gè)方法中 LayoutMananger 向列表中填充 item 的時(shí)候,就會(huì)向 Recycler 中直接獲取一個(gè) ViewHolder,如果獲取不到的話,就意味著緩存中沒有 ViewHolder,那么此時(shí)把 item 填充到列表上的時(shí)候就會(huì)創(chuàng)建一個(gè) ViewHolder ,然后再綁定數(shù)據(jù),這樣是非常浪費(fèi)資源的,所以再復(fù)用之前要先回收,從緩存中獲取item view,在調(diào)用 addView 方法的時(shí)候,是會(huì)判斷這個(gè) view是否已經(jīng)添加過了,所以我們也不用擔(dān)心造成重復(fù)添加 view 的情況.
來看看這個(gè)方法的實(shí)現(xiàn).
RecyclerView.Java
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
很簡單,首先對(duì)屏幕上可見的 view 來一次遍歷,然后逐個(gè)調(diào)用 scrapOrRecycleView 方法,注意這里是屏幕上可見的,而當(dāng)我們初次加載而不是調(diào)用刷新方法的時(shí)候,childCount 是為0的,但是當(dāng) RecyclerView 的大小是不確定的并且item的大小也是不確定的時(shí)候,就會(huì)把能能加載的item 數(shù)量都算進(jìn)去,這也證明上面提到過的要盡量給一個(gè)確定的大小,而不是wrap_content,當(dāng)然 RecyclerView 給 match_parent 是沒有這種效果的.
來看看這個(gè) scrapOrRecycleView
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
在回收之前呢,他會(huì)判斷這個(gè) viewHolder 是否是 isInvalid,這個(gè) isInvalid 就是 item對(duì)應(yīng)的 viewHolder 里面存儲(chǔ)的數(shù)據(jù)已經(jīng)無效了,發(fā)生了變化了,就說明這個(gè) VeiwHolder 已經(jīng)無效了.還有 isRemoved 判斷是否移除,還有這個(gè) hasStableIds 代表列表上的每個(gè) item 是否都有著一個(gè)唯一的 long 類型的身份標(biāo)識(shí),這個(gè)方法在默認(rèn)情況下都是 false.
那么在什么情況下才會(huì)走這個(gè)分支呢?數(shù)據(jù)無效了,且沒有移除,會(huì)想到 notifyDataSetChanged 的時(shí)候,recyclerView 會(huì)認(rèn)為列表上的數(shù)據(jù)集已經(jīng)全面發(fā)生了變化,列表上的 item 都需要重新綁定數(shù)據(jù),那么就會(huì)給列表上的viewHolder 標(biāo)記上一個(gè)無效的flag,在這里會(huì)調(diào)用 recycler.recyclerViewHolderInternal(viewHolder)
來跟進(jìn)一下這個(gè)方法
void recycleViewHolderInternal(ViewHolder holder) {
...
if (DEBUG && mCachedViews.contains(holder)) {
throw new IllegalArgumentException("cached view received recycle internal? "
+ holder + exceptionLabel());
}
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
...
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
...
}
這個(gè)方法會(huì)回收 viewHolder ,mCachedViews.contains(holder) 回收的時(shí)候會(huì)判斷是否存儲(chǔ)在了 mCachedViews 里面,如果沒有還會(huì)判斷 mViewCacheMax 里面的緩存數(shù)量是否已經(jīng)溢出了,if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) 如果已經(jīng)溢出了,就調(diào)用 recycleCachedViewAt(0); 把最先添加進(jìn)來的給移除掉,然后放入到 viewPool 里面,然后接著就調(diào)用 mCachedViews.add 會(huì)把我們需要緩存的 viewHolder 添加到 mCachedViews 里面,如果設(shè)置不用mCachedViewed緩存的話,那回收時(shí)就扔進(jìn)ViewPool里等待復(fù)用.
好的,回到 scrapOrRecycleView
else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
如果不是 notifyDataSetChanged,notifyItemChanged 產(chǎn)生的更新,我們看看這個(gè)方法 recycler.scrapView(view)
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
在這里面他會(huì)判斷viewHolder 是否被移除,是否無效,有沒有被更新,如果沒有的話.
那么 recyclerView 會(huì)認(rèn)為他在本次布局階段依舊是想留在屏幕上面的,所以會(huì)被存儲(chǔ)到 mAttachedScrap里面去.否則也就放入了 mChangedScrap 里面.
好了,到這里這個(gè)方法就走完了,還記得是從哪里過來的么?回顧一下吧.
在 onLayoutChildren 里面說到了
detachAndScrapAttachedViews(recycler);
→scrapOrRecycleView(recycler, i, v);
→recycler.recycleViewHolderInternal(viewHolder);和recycler.scrapView(view);
好的,回到 onLayoutChildren 我們繼續(xù)往下看.
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
// 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
...
// fill towards end
...
} else {
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
final int 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;
}
}
...
}
發(fā)現(xiàn)了好多 fill ,什么意思呢?其實(shí)這里面是開始進(jìn)入了向列表上填充item的工作,首先是判斷了是否需要倒序布局,我們沒有主動(dòng)設(shè)置都是 false,來看 else分支,// fill towards start 之前說過,他會(huì)從錨點(diǎn)位置從上往下填充item ,填充的時(shí)候就是調(diào)用了這個(gè) fill 方法, // fill towards end 之后呢又會(huì)從錨點(diǎn)位置從下往上進(jìn)行填充,這兩次填充都是調(diào)用了 fill 方法,跟進(jìn)一下,fill(recycler, mLayoutState, state, false);
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
...
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
}
...
return start - layoutState.mAvailable;
}
我們看到這里開啟了一個(gè) while 循環(huán),還有個(gè) remainingSpace 條件,意思為在當(dāng)前方向上是否還有可用空間,在這里面實(shí)際上就是調(diào)用了 layoutChunk 方法,把 item 一個(gè)個(gè)填充到列表上面,跟進(jìn)一下
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
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);
}
} 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 看下下面這個(gè)方法的大概,把一條item填充到列表上面的時(shí)候,就是從 recycler 緩沖池里面得到一個(gè) veiw,得到這個(gè)對(duì)象之后就會(huì)調(diào)用 layoutManager 的 addView 方法,在這個(gè) addView 方法里面,首先會(huì)判斷這個(gè) view 對(duì)應(yīng)的 view holder是否是即將刪除的 view ,如果是則把他添加到即將刪除的這個(gè)集合當(dāng)中.
接下來就是調(diào)用 measureChildWithMargins(view, 0, 0); 這個(gè)方法,這個(gè)方法在測量的時(shí)候考慮到了item上下左右的margin以及 Decorated 需要的額外空間,在接下來會(huì)調(diào)用 layoutDecoratedWithMargins(view, left, top, right, bottom); 把 view擺到列表上適當(dāng)?shù)奈恢蒙先?linearLayoutMananger 逐一像列表上填充item.
回過頭來看一下他在填充的時(shí)候獲取view的工作流程,View view = layoutState.next(recycler);這個(gè)方法其實(shí)就是復(fù)用機(jī)制的一個(gè)開始.
先說一下復(fù)用的大體流程:
LayoutMananger 每次像列表上填充 item 的時(shí)候,都會(huì)向 recycler 索取一個(gè) viewHolder,索取 viewHolder 的時(shí)候,recycler 就會(huì)按照優(yōu)先級(jí)先到,mAttachedScrap和mChangedScrap 這兩個(gè)一級(jí)緩存中查找是否有可復(fù)用的 viewHolder,這里面存儲(chǔ)的 viewHolder 不需要重新綁定數(shù)據(jù),實(shí)際上他們兩個(gè)存儲(chǔ)的 viewHolder 也都是屏幕內(nèi)的 viewHodlder.
如果一級(jí)緩存查找不到,就會(huì)向二級(jí)緩存 mCacheViews 中查找,如果找到了,還需要做一個(gè)位置移植性的校驗(yàn),因?yàn)?mCacheViews 里面存儲(chǔ)的 viewHodler 是被滑出屏幕的,那么只有相同位置的才可以直接復(fù)用,如果被下面滑上來的復(fù)用了,就需要重新綁定數(shù)據(jù)了.
如果二級(jí)緩存里面還是找不到,就回去三級(jí)緩存,允許開發(fā)者自定義的 mViewCacheExt 中去查找,而實(shí)際上我們基本沒有定義.
所以又會(huì)向四級(jí)緩存 mRecyclerPool 中查找在這里查找的時(shí)候會(huì)根據(jù) item 的 viewType 去查找,如果有就返回,如果沒有最后還是會(huì)調(diào)用 adapter 的 createViewHolder 來創(chuàng)建并返回.
看一下 layoutState.next
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
可以看到只有一個(gè) recycler.getViewForPosition 跟進(jìn)一下
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
發(fā)現(xiàn)了 tryGetViewHolderForPositionByDeadline ,嘗試性的獲取一個(gè) viewHolder ,繼續(xù)跟進(jìn).
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
...
}
這個(gè)方法里面首先調(diào)用了 holder = getChangedScrapViewForPosition(position); 從名字也能看出是從一級(jí)緩存 mChangedScrap 中去獲取,看看這個(gè)方法吧.
ViewHolder getChangedScrapViewForPosition(int position) {
// If pre-layout, check the changed scrap for an exact match.
final int changedScrapSize;
if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
return null;
}
// find by position
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
// find by id
if (mAdapter.hasStableIds()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
final long id = mAdapter.getItemId(offsetPosition);
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
}
return null;
}
首先判斷了一下,啥也沒有就返回了,接下來就是對(duì) mChangedScrap 進(jìn)行了一個(gè)遍歷,遍歷的時(shí)候會(huì)去校驗(yàn)這個(gè) viewHolder 是否沒有被復(fù)用過!holder.wasReturnedFromScrap(),還會(huì)判斷這個(gè) viewHolder 之前列表上的位置和正要填充的位置是否一致,如果一致就會(huì)返回,下面的通過id尋找,代碼基本差不多.
回到 tryGetViewHolderForPositionByDeadline ,如果上面的方法沒有找到,就會(huì)調(diào)用 getScrapOrHiddenOrCachedHolderForPosition 這個(gè)方法繼續(xù)去尋找.
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
...
}
跟進(jìn)下 getScrapOrHiddenOrCachedHolderForPosition
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
if (!dryRun) {
View view = mChildHelper.findHiddenNonRemovedView(position);
if (view != null) {
// This View is good to be used. We just need to unhide, detach and move to the
// scrap list.
final ViewHolder vh = getChildViewHolderInt(view);
mChildHelper.unhide(view);
int layoutIndex = mChildHelper.indexOfChild(view);
if (layoutIndex == RecyclerView.NO_POSITION) {
throw new IllegalStateException("layout index should not be -1 after "
+ "unhiding a view:" + vh + exceptionLabel());
}
mChildHelper.detachViewFromParent(layoutIndex);
scrapView(view);
vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
return vh;
}
}
// Search in our first-level recycled view cache.
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
// invalid view holders may be in cache if adapter has stable ids as they can be
// retrieved via getScrapOrCachedViewForId
if (!holder.isInvalid() && holder.getLayoutPosition() == position
&& !holder.isAttachedToTransitionOverlay()) {
if (!dryRun) {
mCachedViews.remove(i);
}
if (DEBUG) {
Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ ") found match in cache: " + holder);
}
return holder;
}
}
return null;
}
,在這里面首先從 mAttachedScrap 里面去獲取,同樣是一個(gè)遍歷,也會(huì)進(jìn)行一個(gè)檢驗(yàn),
同樣判斷了這個(gè)viewHolder之前是否沒有被復(fù)用過,這個(gè)viewHolder之前列表上的位置和正要填充的位置是否一致,這個(gè)viewHolder 之前的數(shù)據(jù)有沒有被置為無效,這個(gè)viewHolder之前有沒有被移除,都符合就返回,如果沒有找到,就會(huì)調(diào)用下面的方法 findHiddenNonRemovedView,這個(gè)方法是去向正在做刪除動(dòng)畫的集合中去查找是否有滿足可復(fù)用的 viewHolder,這里是向 mHiddenViews 里面尋找,但它并不屬于四級(jí)緩存里面的,當(dāng)刪除動(dòng)畫執(zhí)行完的時(shí)候這個(gè)集合里面的數(shù)據(jù)也就被移除了,如果說這里面也沒有找到,就會(huì)去mCachedViews里面去尋找,同樣是一個(gè)遍歷,然后檢驗(yàn).
好,繼續(xù)回到上面的 tryGetViewHolderForPositionByDeadline 中,通過 getScrapOrHiddenOrCachedHolderForPosition 獲取 viewHolder,我們來看看如果返回了一個(gè) viewHolder 會(huì)怎么樣,如果 viewHolder 不為空,會(huì)調(diào)用validateViewHolderForOffsetPosition 這個(gè)方法,來跟進(jìn)一下.
boolean validateViewHolderForOffsetPosition(ViewHolder holder) {
// if it is a removed holder, nothing to verify since we cannot ask adapter anymore
// if it is not removed, verify the type and id.
...
if (!mState.isPreLayout()) {
// don't check type if it is pre-layout.
final int type = mAdapter.getItemViewType(holder.mPosition);
if (type != holder.getItemViewType()) {
return false;
}
}
if (mAdapter.hasStableIds()) {
return holder.getItemId() == mAdapter.getItemId(holder.mPosition);
}
return true;
}
這里我們我們看到了他判斷了viewHolder 對(duì)應(yīng)的 itemType 和正在填充的 viewType 是否一致,type != holder.getItemViewType().
接下來還會(huì)判斷,如果給 adapter開啟了這個(gè)item
的唯一身份標(biāo)識(shí),還會(huì)判斷這個(gè)item前后id的一致性,只有都通過才會(huì)復(fù)用.
繼續(xù)回到 tryGetViewHolderForPositionByDeadline
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder"
+ exceptionLabel());
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view." + exceptionLabel());
}
}
}
...
}
如果 getScrapOrHiddenOrCachedHolderForPosition 返回 null,還會(huì)去 mViewCacheExtension 中去查找,而一般情況下開發(fā)者不會(huì)去自定義這個(gè)緩存策略,所以基本為空.
繼續(xù)往下看
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
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);
}
}
...
}
最后會(huì)去 mRecyclerPool 里面根據(jù) viewType 去查找,holder = getRecycledViewPool().getRecycledView(type) ,如果這里還是查找不到,那么就會(huì)調(diào)用 holder = mAdapter.createViewHolder(RecyclerView.this, type);去創(chuàng)建一個(gè)新的 viewHolder.
總結(jié)
插拔式的設(shè)計(jì)模式增加靈活性
盡量指定RecyclerView 和 item 的寬和高
盡量使用定向刷新 notyfyItemChanged