RecyclerView 源碼分析
本文原創(chuàng),轉(zhuǎn)載請注明出處。
歡迎關(guān)注我的 簡書 ,關(guān)注我的專題 Android Class 我會(huì)長期堅(jiān)持為大家收錄簡書上高質(zhì)量的 Android 相關(guān)博文。
寫在前面:
RecyclerView 是一個(gè)越用越優(yōu)雅的控件,相信大家對(duì)于 RecyclerView 的使用也已經(jīng)比較熟悉了。其功能的高度解耦化,規(guī)范 ViewHolder 的寫法,以及對(duì)動(dòng)畫友好支持,都是它與傳統(tǒng)控件 ListView 的區(qū)別。而無論 ListView 還是 RecyclerView,本質(zhì)上都是在有限的屏幕之上,展示大量的內(nèi)容。所以復(fù)用的邏輯,就成了它們最最重要的核心原理,本文主要目的就是探究 RecyclerView 的復(fù)用原理。
RecyclerView 的幾大模塊
LayoutManager
負(fù)責(zé) RecyclerView 中,控制 item 的布局方向RecyclerView.Adapter
為 RecyclerView 承載數(shù)據(jù)ItemDecoration
為 RecyclerView 添加分割線ItemAnimator
控制 RecyclerView 中 item 的動(dòng)畫
剛剛我們提到 RecyclerView 的高度解耦,就是通過以上對(duì)象各司其職,來實(shí)現(xiàn) RecyclerView 的基本功能。RecyclerView 無論多么復(fù)雜,本質(zhì)上也是一個(gè)自定義 View,本文的重點(diǎn)就是緩存原理分析,不過在此之前,我們還是簡單地分別介紹下以上中各個(gè)模塊的源碼。
ItemDecoration
public void addItemDecoration(ItemDecoration decor, int index) {
if (mLayout != null) {
mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
+ " layout");
}
if (mItemDecorations.isEmpty()) {
setWillNotDraw(false);
}
if (index < 0) {
mItemDecorations.add(decor);
} else {
// 指定添加分割線在集合中的索引
mItemDecorations.add(index, decor);
}
markItemDecorInsetsDirty();
// 重新請求 View 的測量、布局、繪制
requestLayout();
}
mItemDecorations
是一個(gè) ArrayList
,我們將 ItemDecoration
也就是分割線對(duì)象,添加到其中。接著我們看下 markItemDecorInsetsDirty
這個(gè)方法做了些什么。
void markItemDecorInsetsDirty() {
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
final View child = mChildHelper.getUnfilteredChildAt(i);
((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
}
mRecycler.markItemDecorInsetsDirty();
}
這個(gè)方法首先遍歷了 RecyclerView 和 LayoutManager 的所有子 View,將其子 View 的 LayoutParams 中的 mInsetsDirty
屬性置為 true。接著調(diào)用了 mRecycler.markItemDecorInsetsDirty()
,Recycler 是 RecyclerView 的一個(gè)內(nèi)部類,就是它管理著 RecyclerView 的復(fù)用邏輯。這個(gè)我們一會(huì)再細(xì)談。
void markItemDecorInsetsDirty() {
final int cachedCount = mCachedViews.size();
for (int i = 0; i < cachedCount; i++) {
final ViewHolder holder = mCachedViews.get(i);
LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams();
if (layoutParams != null) {
layoutParams.mInsetsDirty = true;
}
}
}
mCachedViews
見名知意,也就是 RecyclerView 緩存的集合,相信你也看到了,RecyclerView 的緩存單位是 ViewHolder。我們在 ViewHolder 中取出 itemView,然后獲得 LayoutParams,將其 mInsetsDirty 字段一樣置為 true。
mInsetsDirty 字段的作用其實(shí)是一種優(yōu)化性能的緩存策略,添加分割線對(duì)象時(shí),無論是 RecyclerView 的子 view,還是緩存的 view,都將其置為 true,接著就調(diào)用了 requestLayout
方法。
這里簡單說一下 requestLayout
方法用一種責(zé)任鏈的方式,層層向上傳遞,最后傳遞到 ViewRootImpl,然后重新調(diào)用 view 的 measure、layout、draw 方法來展示布局。
我們在 RecyclerView 中搜索 mItemDecorations 集合,看看他是在什么時(shí)刻操作 ItemDecoration 這個(gè)分割線對(duì)象的。
在 onDraw
中:
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
在 draw
方法中:
@Override
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
}
可以看到在 View 的以上兩個(gè)方法中,分別調(diào)用了 ItemDecoration 對(duì)象的 onDraw
onDrawOver
方法。
這兩個(gè)抽象方法,由我們繼承 ItemDecoration 來自己實(shí)現(xiàn),他們區(qū)別就是 onDraw
在 item view 繪制之前調(diào)用,onDrawOver
在 item view 繪制之后調(diào)用。
所以繪制順序就是 Decoration 的 onDraw,ItemView的 onDraw,Decoration 的 onDrawOver。
(好像越寫越多...收不住了...)
我們在 onDraw
和 onDrawOver
方法中就可以繪制 drawable 對(duì)象了。此時(shí)分割線就展現(xiàn)出來了。還記得剛才的 mInsetsDirty
字段嗎?在添加分割線的時(shí)候,無論是 RecyclerView 子 View,還是緩存中的 View,其 LayoutParams 中的 mInsetsDirty 屬性,都被置為 true。 我們來解釋一下這個(gè)字段的作用:
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
// 當(dāng) mInsetsDirty 為 false,說明 mDecorInsets 緩存可用
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);
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;
}
來解釋一下這段代碼,首先 getItemDecorInsetsForChild
方法是在 RecyclerView 進(jìn)行 measureChild 時(shí)調(diào)用的。目的就是為了取出 RecyclerView 的 ChildView 中的分割線屬性 --- 在 LayoutParams 中緩存的 mDecorInsets 。而 mDecorInsets 就是 Rect 對(duì)象, 其保存記錄的是所有添加分割線需要的空間累加的總和,由分割線的 getItemOffsets
方法影響。
最后在 measureChild 方法里,將分割線 ItemDecoration 的尺寸加入到 itemView 的 padding 中。
但是大家都知道緩存并不是總是可用的,mInsetsDirty 這個(gè) boolean 字段來記錄它的時(shí)效性,當(dāng) mInsetsDirty 為 false 時(shí),說明緩存可用,直接取出可以,當(dāng) mInsetsDirty 為 true 時(shí),說明緩存的分割線屬性就需要重新計(jì)算了。
到此,關(guān)于 RecyclerView 添加分割線 ItemDecoration 的源碼分析,也就基本結(jié)束了。
ItemAnimator
如果有人問我,在什么情況下你絕對(duì)會(huì)選擇 RecyclerView,而不是 ListView?如果需求對(duì) Item 的動(dòng)畫有一定要求,這絕對(duì)是我選擇 RecyclerView 的重要原因之一。ListView 如果要做 Item 的增刪動(dòng)畫,那可要費(fèi)很大勁兒,而 RecyclerView 自身對(duì)動(dòng)畫就有很好的支持。
public void setItemAnimator(ItemAnimator animator) {
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
mItemAnimator.setListener(null);
}
mItemAnimator = animator;
if (mItemAnimator != null) {
mItemAnimator.setListener(mItemAnimatorListener);
}
}
這個(gè)方法就是為 RecyclerView 設(shè)置動(dòng)畫的入口,邏輯就是清除舊的 Listener,設(shè)置新的 Listener。這個(gè)沒什么可多說的,我們這次直接來看看 ItemAnimator 這個(gè)類。
當(dāng)我第一次粗略的看 RecyclerView 的源碼之時(shí),感覺這也太多了吧,一萬兩千多行,這得多復(fù)雜啊。后來再看看,才知道,RecyclerView 是把
ItemAnimator 、LayoutManager 等等這些模塊都當(dāng)內(nèi)部類寫在了一塊。。。我不知道這樣設(shè)計(jì)的目的是啥,除了寫著方便之外可能找不到其他理由了,so... 可能是 google 的程序員偷個(gè)懶吧 - -
來看 ItemAnimator 類:
public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state,
@NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags,
@NonNull List<Object> payloads) {
return obtainHolderInfo().setFrom(viewHolder);
}
public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state,
@NonNull ViewHolder viewHolder) {
return obtainHolderInfo().setFrom(viewHolder);
}
這兩個(gè)函數(shù)看命名也能猜一個(gè)大概,其目的就是為了記錄在 RecyclerView 布局之前/之后,必要的一些 layout 信息保存在 ItemHolderInfo 中,ItemHolderInfo 這個(gè)類就是用來記錄當(dāng)前 ItemView 的位置信息
recordPreLayoutInformation
來記錄 layout 之前的狀態(tài)信息,這個(gè)方法在 dispatchLayoutStep1
之中調(diào)用。
dispatchLayoutStep1
這個(gè)方法做了什么呢,來看看注釋就一目了然:
/**
* 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
*/
這是布局的第一步:
進(jìn)行 adapter 布局的更新,決定執(zhí)行哪個(gè)動(dòng)畫,保存當(dāng)前 view 的信息,如果有必要,運(yùn)行 predictive layout。
相同的 recordPostLayoutInformation
方法來記錄 layout 過程完成時(shí),ItemView 的信息。
它在 dispatchLayoutStep3
方法中調(diào)用,dispatchLayoutStep3
方法作用:
/**
* The final step of the layout where we save the information about views for animations,
* trigger animations and do any necessary cleanup.
*/
layout 的最后一個(gè)步驟,保存 view 動(dòng)畫的信息,執(zhí)行動(dòng)畫,狀態(tài)清理。當(dāng)然也有 dispatchLayoutStep2
方法,他們?nèi)齻€(gè)方法依次在 onLayout
方法的 dispatchLayout
方法調(diào)用。
dispatchLayout
方法目的是 layout RecyclerView 的 childview,并且記錄動(dòng)畫執(zhí)行的過程、變更。
現(xiàn)在知道了這兩個(gè) API 的作用就是在 layout 前后記錄 itemview 動(dòng)畫的狀態(tài),保存在 ItemHolderInfo
中,我們繼續(xù)尋找執(zhí)行動(dòng)畫的 API。
- animateDisappearance
當(dāng) ViewHolder 從 RecyclerView 的 layout 中移除時(shí),調(diào)用 - animateAppearance
當(dāng) ViewHolder 添加進(jìn) RecyclerView 時(shí),調(diào)用 - animatePersistence
當(dāng) ViewHolder 已經(jīng)添加進(jìn) layout 還未移除時(shí),調(diào)用 - animateChange
當(dāng) ViewHolder 已經(jīng)添加進(jìn) layout 還未移除,并且調(diào)用了 notifyDataSetChanged 時(shí),調(diào)用。
以上四個(gè) api 就是為 RecyclerView 執(zhí)行動(dòng)畫的。
調(diào)用的時(shí)機(jī)和方式是如何的呢?
/**
* The callback to convert view info diffs into animations.
*/
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
new ViewInfoStore.ProcessCallback() {
@Override
public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
@Nullable ItemHolderInfo postInfo) {
mRecycler.unscrapView(viewHolder);
animateDisappearance(viewHolder, info, postInfo);
}
@Override
public void processAppeared(ViewHolder viewHolder,
ItemHolderInfo preInfo, ItemHolderInfo info) {
animateAppearance(viewHolder, preInfo, info);
}
@Override
public void processPersistent(ViewHolder viewHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
viewHolder.setIsRecyclable(false);
if (mDataSetHasChangedAfterLayout) {
// since it was rebound, use change instead as we'll be mapping them from
// stable ids. If stable ids were false, we would not be running any
// animations
if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo,
postInfo)) {
postAnimationRunner();
}
} else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
postAnimationRunner();
}
}
@Override
public void unused(ViewHolder viewHolder) {
mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
}
};
mViewInfoProcessCallback
是一個(gè)匿名內(nèi)部類,在其回調(diào)方法中,分別執(zhí)行了以上四個(gè)關(guān)于動(dòng)畫的 api。
繼續(xù)跟進(jìn),看看mViewInfoProcessCallback
這個(gè)接口是什么時(shí)候被調(diào)用執(zhí)行的:
// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
執(zhí)行觸發(fā)方法的位置就在我們剛才提到的 dispatchLayoutStep3
方法中,去根據(jù)一些保存的 flag 狀態(tài)去觸發(fā)動(dòng)畫。
LayoutManager
與其他綁定 adapter 展示數(shù)據(jù)的控件,比如 ListView、GrideView 相比,RecyclerView 允許自定義規(guī)則去放置子 view,這個(gè)規(guī)則的控制者就是 LayoutManager。一個(gè) RecyclerView 如果想展示內(nèi)容,就必須設(shè)置一個(gè) LayoutManager。
我們按照慣例來看設(shè)置 LayoutManager 的入口:
public void setLayoutManager(LayoutManager layout) {
if (layout == mLayout) {
return;
}
// 停止滑動(dòng)
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
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
// 移除并回收視圖
mLayout.removeAndRecycleAllViews(mRecycler);
// 回收廢棄視圖
mLayout.removeAndRecycleScrapInt(mRecycler);
mRecycler.clear();
if (mIsAttached) {
mLayout.dispatchDetachedFromWindow(this, mRecycler);
}
mLayout.setRecyclerView(null);
mLayout = null;
} else {
mRecycler.clear();
}
// this is just a defensive measure for faulty item animators.
mChildHelper.removeAllViewsUnfiltered();
mLayout = layout;
if (layout != null) {
if (layout.mRecyclerView != null) {
throw new IllegalArgumentException("LayoutManager " + layout +
" is already attached to a RecyclerView: " + layout.mRecyclerView);
}
mLayout.setRecyclerView(this);
if (mIsAttached) {
mLayout.dispatchAttachedToWindow(this);
}
}
mRecycler.updateViewCacheSize();
requestLayout();
}
這段代碼主要做了一下幾件事:
當(dāng)之前設(shè)置過 LayoutManager 時(shí),移除之前的視圖,并緩存視圖在 Recycler 中,將新的 mLayout 對(duì)象與 RecyclerView 綁定,更新緩存 View 的數(shù)量。最后去調(diào)用 requestLayout
,重新請求 measure、layout、draw。
關(guān)于 RecyclerView 的緩存我們一會(huì)再研究,先看看設(shè)置的 LayoutManager 是在何時(shí)何處發(fā)揮作用的吧:
LayoutManager 作為 RecyclerView 的一個(gè)抽象內(nèi)部類,大概有3000行代碼,方法數(shù)還很多,看著頭都大了。用有限的時(shí)間去了解每一個(gè)方法的作用顯然不現(xiàn)實(shí)。LayoutManager 的作用就是為 RecyclerView 放置子 view,所以我直接去定位 RecyclerView 的 onLayout 和 onMeasure 方法,研究一下 LayoutManager 的一些關(guān)鍵函數(shù)的作用。
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || 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 {
// 省略非自動(dòng)測量的情況
}
}
來分析一下 onMeasure
方法,mAutoMeasure
字段用來標(biāo)記是否使用 RecyclerView 的默認(rèn)規(guī)則進(jìn)行自動(dòng)測量,否則就必須在 LayoutManager 中自己實(shí)現(xiàn) onMeasure 來進(jìn)行測量。LinearLayoutManager 的 mAutoMeasure
字段屬性就被設(shè)置成為了 true。
所以我們重點(diǎn)來看mAutoMeasure
為 true 時(shí),測量的規(guī)則。
當(dāng) RecyclerView 的 MeasureSpec
為 MeasureSpec.EXACTLY
時(shí),這個(gè)時(shí)候可以直接確定 RecyclerView 的寬高,所以 return 退出測量。當(dāng) RecyclerView 的寬高為不為 EXACTLY 時(shí),首先進(jìn)行的測量步驟就是 dispatchLayoutStep1
,這個(gè)我們在分析動(dòng)畫源碼的時(shí)候提到過。dispatchLayoutStep1
的作用總結(jié)起來就是記錄 layout 之前,view 的信息。
接著繼續(xù)調(diào)用了 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).
*/
private void dispatchLayoutStep2() {
...
// Step 2: Run layout
...
mLayout.onLayoutChildren(mRecycler, mState);
...
mState.mLayoutStep = State.STEP_ANIMATIONS;
}
dispatchLayoutStep2
比較關(guān)鍵的就是以上代碼展示的這兩步,onLayoutChildren
這個(gè)函數(shù)由 LayoutManager 實(shí)現(xiàn),來規(guī)定放置子 view 的算法,尋找錨點(diǎn)填充 view,錨點(diǎn)的尋找和填充 view 的方式,這里就不細(xì)說了。因?yàn)槠鶎?shí)在是太長。具體可以直接去看 LinearLayoutManager 的實(shí)現(xiàn)方式。
第二步就是將 mState.mLayoutStep 置為 State.STEP_ANIMATIONS,剛才我們忘記說 mLayoutStep 這個(gè)屬性了,從它的命名就知道它是來標(biāo)記 layout 這個(gè)過程進(jìn)行到哪一步了。在 dispatchLayoutStep1
中 mState.mLayoutStep 被置為 State.STEP_LAYOUT,記錄 layout 的步驟是什么原因呢?來看看 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;
}
跟進(jìn) dispatchLayout
void dispatchLayout() {
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();
} else if (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();
}
看到 mState.mLayoutStep
的作用了吧,當(dāng)我們在 onMeasure
方法中已經(jīng)調(diào)用過 dispatchLayoutStep1
、 dispatchLayoutStep2
時(shí),在 onLayout
方法中只會(huì)調(diào)用 dispatchLayoutStep3
,dispatchLayoutStep3
方法我們在動(dòng)畫部分的源碼講解過。
回想一下,什么情況下會(huì)在 onMeasure
方法中直接調(diào)用 dispatchLayoutStep1
、 dispatchLayoutStep2
? 就是 RecyclerView 的 MeasureSpec 不為 EXACTLY 時(shí),這個(gè)情況下 RecyclerView 不能自己確定自身的寬高,只能在測量、布局了子 view 才能確定自己的寬高。所以在 onMeasure 的時(shí)候就調(diào)用了 dispatchLayoutStep1
、 dispatchLayoutStep2
,在 onLayout 僅僅調(diào)用 dispatchLayoutStep3
方法就可以了。
如果文字表述的不夠清晰,這里來一張圖:
最后在這個(gè)章節(jié)中,總結(jié)一下 LayoutManager 的作用:
- 協(xié)助 RecyclerView 完成 onMeasure 過程
- 通過 onLayoutChildren 完成對(duì)子 view 的布局
- 滾動(dòng)子視圖
- 滾動(dòng)過程中判斷何時(shí)添加 view ,何時(shí)回收 view,也就是對(duì)緩存時(shí)機(jī)的判斷。
Recycler
這篇文章我在工作之余寫寫停停大概花了一周,終于到了本文的最后一個(gè)模塊 -- recycler
在上文中反復(fù)提到,Recycler 就是控制 RecyclerView 緩存的核心類。理解了它的工作過程,就可以弄明白 RecyclerView 如何回收 view,如何復(fù)用 view 的。
在上一個(gè)章節(jié),我們說過了 LayoutManager 其實(shí)就是 Recycler 的控制者,由 LayoutManager 來決定調(diào)用 Recycler 關(guān)鍵方法的時(shí)機(jī),口說無憑,就直接來看看 LinearLayoutManager 的代碼吧:
首先來看最關(guān)鍵的部分 onLayoutChildren
方法,這個(gè)方法是在 RecyclerView 的 dispatchLayoutStep2
階段調(diào)用的。
onLayoutChildren
方法比較長,它核心的作用就是尋找一個(gè)錨點(diǎn),為 RecyclerView 填充子 View。在 onLayoutChildren
調(diào)用的 fill
方法就是真正開始填充 layout 的方法。
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;
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
}
return start - layoutState.mAvailable;
}
這就是省略了部分代碼的 fill
方法,其中的 while 循環(huán)中,通過判斷 LaytouState 中保存的狀態(tài)來不斷的通過 LayoutChunk
方法填充 view。所以接著再看 LayoutChunk
方法。
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
if (view == null) {
// if we are laying out views in scrap, this may return null which means there is
// no more items to layout.
result.mFinished = true;
return;
}
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
}
以上就是 layoutChunk
的部分代碼,可以看到通過 next
方法取出來 view ,并且通過 addView
添加到 RecyclerView 里面去,繼續(xù)跟進(jìn)next
方法,看看它是用什么方式和規(guī)則取出來 View 的。
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
一路看到這里,我們的 Recycler 終于出現(xiàn)了...跟進(jìn) getViewForPosition
方法吧。
在此之前可以先看看 Recycler 幾個(gè)重要的成員變量,便于我們更好的認(rèn)識(shí) RecyclerView 的緩存結(jié)構(gòu):
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
提前說一下,RecyclerView 其實(shí)可以算作是四級(jí)緩存、mAttachedScrap 、mCachedViews 、mViewCacheExtension、mRecyclerPool 這四個(gè)對(duì)象就是作為每一級(jí)緩存的結(jié)構(gòu)的。
getViewForPosition
方法:
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
跟進(jìn) tryGetViewHolderForPositionByDeadline
方法,一段一段的閱讀這個(gè)方法代碼。
在文章之初我們就說過,RecyclerView 的緩存單元是 ViewHolder,這個(gè)tryGetViewHolderForPositionByDeadline
方法就完整的展示了如何在每個(gè)層級(jí)的緩存中,取出來 ViewHolder,下面我們一步步的分析一下:
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
第一步,從 mChangedScrap 中嘗試取出緩存的 ViewHolder ,若不存在,返回空。
// 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;
}
}
}
如果在第一步發(fā)現(xiàn)沒有緩存的 ViewHolder,則去 mAttachedScrap 中取,當(dāng)然 mAttachedScrap 中沒有怎么辦呢,接著去 mHiddenViews 里面去找,如果還沒有,繼續(xù)從 mCachedViews 中取緩存的 ViewHolder ,這一系列操作是緩存層級(jí)的第二步。
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount());
}
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
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");
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view.");
}
}
}
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);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
}
在 getScrapOrCachedViewForId
方法中,根據(jù) id 依次在 mAttachedScrap 、mCachedViews 集合中尋找緩存的 ViewHolder,如果都不存在,則在 ViewCacheExtension 對(duì)象中尋找緩存,ViewCacheExtension 這個(gè)類需要使用者通過 setViewCacheExtension
方法傳入,RecyclerView 自身并不會(huì)實(shí)現(xiàn)它,一般正常的使用也用不到。緩存層級(jí)的第三步,我們就分析完畢了,來看最后一步。
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);
}
}
}
這段代碼是從 RecycledViewPool 中取根據(jù) type 取 ViewHolder,對(duì)于 RecycledViewPool 下面多說幾句:
static class ScrapData {
ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
從這里我們知道,RecycledViewPool 其實(shí)是一個(gè) SparseArray 保存 ScrapData 對(duì)象的結(jié)構(gòu)。根據(jù) type 緩存 ViewHolder,每個(gè) type,默認(rèn)最多保存5個(gè) ViewHolder。上面提到的 mCachedViews 這個(gè)集合默認(rèn)最大值是 2 。
RecycledViewPool 可以由多個(gè) ReyclerView 共用。
RecycledViewPool 就是緩存結(jié)構(gòu)中的第四級(jí)緩存了,如果 RecycledViewPool 中依然沒有緩存的 ViewHolder 怎么辦呢?
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
這個(gè)時(shí)候沒有辦法,就只能調(diào)用 mAdapter.createViewHolder(RecyclerView.this, type)
,來創(chuàng)建一個(gè) ViewHolder 了。
到此我們就知道一個(gè) ViewHolder 是如何層層從緩存中取出的了。
寫在后面:
本文從源碼的角度研究了 RecyclerView 的主要模塊和功能,但是 RecyclerView 本身是很復(fù)雜的,要考慮到非常多的情況,光是各種狀態(tài)的記錄就讓人看得很迷,這種復(fù)雜度的控件真不是一般程序員可以搞定的。不過我們不必深究每一塊細(xì)節(jié),將大致的流程梳理在心,也肯定會(huì)有收獲和啟發(fā)。未來打算總結(jié)下 RecyclerView 可能發(fā)生的一些復(fù)用問題。有疑問可以在下方交流。