文章目錄
- 前言
- RecyclerView.Recycler
-
- 主要成員變量
- RecycledViewPool
-
- 成員變量
- 主要方法
-
- getScrapDataForType
- setMaxRecycledViews
- getRecycledView
- putRecycledView
- ViewCacheExtension
- 主要方法
-
- getViewForPosition
- recycleView
- 補充
-
- mChangedScrap 和 mAttachedScrap 中的 View 從何而來
-
- Recycler#scrapView
- 場景分析
-
- 第一次 layout
-
- LinearLayoutManager#onLayoutChildren
- LinearLayoutManager#fill
- LinearLayoutManager#layoutChunk
- 更新清單
-
- Adapter#notifyDataSetChanged
-
- LayoutManager#detachAndScrapAttachedViews
- Recycler#recycleViewHolderInternal
- Adapter#notifyItemChanged
-
- RecyclerView#dispatchLayoutStep1
- LayoutManager#scrapOrRecycleView
- 後記
- 參考
前言
RecyclerView 是一個好用又複雜的控件,其功能的高度解耦化,規範化的 ViewHolder 寫法,以及對動畫的友好支援,都是它與傳統 ListView 的差別。
它有幾大子產品:
- LayoutManager:控制 item 的布局
- RecyclerView.Adapter:為 RecyclerView 提供資料
- ItemDecoration:為 RecyclerView 添加分割線
- ItemAnimator:控制 item 的動畫
- Recycler:負責回收和提供 View,和 RecyclerView 的複用機制相關
下面就從源碼(API 28)角度分析 RecyclerView,RecyclerView 的源碼很複雜,很難在一篇文章内講完,是以打算分幾篇來講,本文是第一篇,将圍繞 RecyclerView 的内部類 Recycler 展開分析:
RecyclerView.Recycler
首先看一下它的作用,源碼上是這樣寫的:
A Recycler is responsible for managing scrapped or detached item views for reuse.
意思就是 Recycler 負責管理廢棄或被 detached 的 item 視圖,以便重複利用。
它有以下幾個成員變量:
主要成員變量
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
這幾個成員變量都和 RecyclerView 的緩存相關,如果按照四級緩存的話,它們可以這樣劃分:
第一級緩存:mAttachedScrap、mChangedScrap
第二級緩存:mCachedViews
第三級緩存:ViewCacheExtension
第四級緩存:RecycledViewPool
後面再介紹 mAttachedScrap、mChangedScrap、mCachedViews 具體存的是哪些 ViewHolder。
現在先了解下 RecycledViewPool 和 ViewCacheExtension這兩個類:
RecycledViewPool
繼續先看官方注釋:
RecycledViewPool lets you share Views between multiple RecyclerViews.
RecycledViewPool 用于在多個 RecyclerView 間共享 View。
在使用時,隻需建立 RecycledViewPool 執行個體,然後調用 RecyclerView 的 setRecycledViewPool(RecycledViewPool) 方法即可。
RecycledViewPool 存儲在 Recycler 中,通過 Recycler 存取。
成員變量
RecycledViewPool 有一個重要的成員變量:
// SparseArray 類似于 key 為 int 類型 的 HashMap
SparseArray<ScrapData> mScrap = new SparseArray<>();
其中 ScrapData 的定義如下:
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP; // 5
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
mScrap 是一個 <int, ScrapData> 的映射,其中 int 代表了 viewType,ScrapData 則存儲了一個 ViewHolder 集合。
主要方法
getScrapDataForType
private ScrapData getScrapDataForType(int viewType) {
ScrapData scrapData = mScrap.get(viewType);
if (scrapData == null) {
scrapData = new ScrapData();
mScrap.put(viewType, scrapData);
}
return scrapData;
}
該方法根據 viewType 擷取相應的 ScrapData,如果該 viewType 還沒有綁定 ScrapData,就新建立一個 ScrapData 并綁定到該 viewType。
setMaxRecycledViews
public void setMaxRecycledViews(int viewType, int max) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mMaxScrap = max;
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
// 從後面開始删除,直到滿足新的容量
while (scrapHeap.size() > max) {
scrapHeap.remove(scrapHeap.size() - 1);
}
}
該方法可以設定相應 viewType 的 View 容量,超出容量時,從後面開始删除,直到滿足新的容量。
getRecycledView
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
該方法根據 viewType 擷取一個 ViewHolder,擷取到的 ViewHolder 将會被移除出 Scrap 堆。擷取不到則傳回 null。
putRecycledView
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
// 容量已滿,不再添加
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
// 重置 ViewHolder,例如清空 flags
scrap.resetInternal();
// 将該 ViewHolder 添加到對應 viewType 的 集合中緩存起來
scrapHeap.add(scrap);
}
該方法也很好了解,根據 ViewHolder 的 viewType 放入 RecycledViewPool 的相應集合中,如果集合已滿,不再添加。
接下來看另一個類:
ViewCacheExtension
ViewCacheExtension 是一個由開發者控制的 View 緩存幫助類,其定義如下:
public abstract static class ViewCacheExtension {
/**
* Returns a View that can be binded to the given Adapter position.
*/
@Nullable
public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
int type);
}
開發者可以實作這個抽象類,通過調用 RecyclerView 的 setViewCacheExtension(ViewCacheExtension) 方法設定,最終将 ViewCacheExtension 存儲在 Recycler 中。
當調用 Recycler 的 getViewForPosition 方法時,如果 attached scrap 和 已經緩存都沒有找到合适的 View,就會調用 ViewCacheExtension 的 getViewForPositionAndType 方法來擷取 View。
需要注意的是,Recycler 不會對這個類做任何緩存處理,是否需要緩存 View 由開發者自己控制。
主要方法
看完這兩個類,現在回到 Recycler 中,看一下 Rcycler 的主要方法:
getViewForPosition
getViewForPosition 方法比較重要,用于擷取某個位置需要展示的 View,如下:
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
繼續看 tryGetViewHolderForPositionByDeadline 方法,該方法會依次從幾個緩存中擷取,分别來看一下:
// 如果是處于預布局階段(先簡單了解為執行 dispatchLayoutStep1 方法)
// (其實下面方法要傳回 ture 還需要開啟“預處理動畫”,這跟動畫有關,先不多說)
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
第一步,從 mChangedScrap 中擷取,擷取不到就傳回 null。
如果 holder 還是為 null,執行下面代碼:
// 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
// 回收無效的 ViewHolder
// ...
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
第二步,根據 position 依次從 mAttachedScrap、mHiddenViews(存儲在 ChildHelper 類)、mCachedViews 中擷取緩存的 ViewHolder。
可以從 mHiddenViews 擷取到緩存的話,就将其從 mHiddenViews 移除并添加到 Scrap 緩存(根據情況添加到 mAttachedScrap 或 mChangedScrap)。可以從 mCacheViews 中擷取到緩存的話,就将其從 mCacheViews 移除。
擷取到後,發現無效的話,将對擷取到的 ViewHolder 進行清理并回收(放入 mCachedViews 或 RecycledViewPool)。
擷取不到,就繼續往下執行:
// 預設傳回 false,可通過 Adapter.setHasStableIds 方法設定該值
if (mAdapter.hasStableIds()) {
// 根據 id 依次在 mAttachedScrap、mCachedViews 中擷取緩存
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
第三步,根據 id 依次從 mAttachedScrap、mCachedViews 中擷取緩存,還沒有擷取到就繼續往下:
// 如果使用者設定了 ViewCacheExtension
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);
// ...
}
}
第四步,從使用者設定的 ViewCacheExtension 中擷取緩存,沒有擷取到就繼續往下:
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
// ...
}
第五步,根據 viewType 從 RecycledViewPool 中得到緩存。
RecycledViewPool 已經是最後一級緩存了,如果這裡也沒有擷取到,隻能通過 Adapter 的 createViewHolder 方法建立一個 ViewHolder:
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
// ...
}
最後小結一下擷取某個位置的 View 的過程:
- 先後根據 position 或 id 從 mChangedScrap 中擷取緩存
- 根據 position 依次從 mAttachedScrap、mHiddenViews(存儲在 ChildHelper 類)、mCachedViews 中擷取緩存
- 根據 id 依次從 mAttachedScrap、mCachedViews 中擷取緩存
- 從使用者設定的 ViewCacheExtension 中擷取緩存
- 從 RecycledViewPool 中得到緩存的廢棄 ViewHolder
- 通過 Adapter 的 createViewHolder 方法建立一個 ViewHolder
recycleView
既然叫 Recycler,那肯定要做回收工作了,recycleView 方法就完成了這些工作,下面看一下該方法的實作:
public void recycleView(@NonNull View view) {
ViewHolder holder = getChildViewHolderInt(view);
// ...
recycleViewHolderInternal(holder);
}
繼續看 recycleViewHolderInternal:
void recycleViewHolderInternal(ViewHolder holder) {
// ...
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
int cachedViewSize = mCachedViews.size();
// 若 CacheViews 達到最大容量(2),将最老的緩存從 CacheViews 移除,并添加到 RecycledViewPool 中
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
// ...
// 将 View 緩存到 mCachedViews 中
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
// 沒有添加到 mCachedViews 的話,就添加到 RecycledViewPool 中
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
// ...
}
可以看到,回收過程主要涉及到兩層緩存,第一層緩存是 CacheViews,在添加時,如果發現原來的 CacheViews 已經達到最大容量,就将最老的緩存從 CacheViews 移除,并添加到 RecycledViewPool。第二層緩存是 RecycledViewPool,如果不能添加到 mCacheViews,就會添加到 RecycledViewPool 中。
補充
mChangedScrap 和 mAttachedScrap 中的 View 從何而來
從前面可以得知,在執行 Recycler 的 recycleView 方法時,會将回收的 View 緩存到 mCahceViews 或 recycledViewPool 中,那麼另外兩個 Scrap 緩存(mChangedScrap 和 mAttachedScrap)中的 View 是何時添加進來的呢?
無論是 mAttachedScrap 還是 mChangedScrap ,它們獲得 View 的途徑都隻有一個,那就是通過 Recycler 的 scrapView 方法。先看下該方法:
Recycler#scrapView
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
// 滿足這幾個條件中的一個就可以進入 if 循環,有機會将 View 緩存到 mAttachedScrap
// 1. ViewHolder 設定了 FLAG_REMOVED 或 FLAG_INVALID
// 2. ViewHolder 沒有設定 FLAG_UPDATE
// 3. 沒有設定動畫或者動畫可以重用該 ViewHolder
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());
}
// 給 ViewHolder 綁定 Recycler
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
}
// 不滿足上述任意一個條件時,将 View 緩存到 mChangedScrap 中
else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
該方法通過判斷 ViewHolder 的 flag 以及是否設定 ItemAnimator 等,決定将 View 緩存到 mAttachedScrap 還是 mChangedScrap。
那麼該方法在何時調用呢?有兩種情況:
- 以 LinearLayoutManager 為例,在它的 onLayoutChildren 方法中,會調用
該方法定義在 RecyclerView 的 LayoutManager 中,它繼續調用 scrapOrRecycleView 方法,如果在該方法符合條件就調用 Recycler 的 scrapView 方法。
- 通過 mHiddenViews 擷取到緩存時,也會調用 scrapView 方法。
場景分析
下面就根據一些場景來分析下 Recycler 是如何進行回收和複用的。
第一次 layout
由于這裡不是專門分析 layout 過程的,就不從 onLayout 開始說了,中間的過程省略掉,它最終會調用到 LayoutManager 的 onLayoutChildren,這裡以 LinearLayoutManager 為例:
LinearLayoutManager#onLayoutChildren
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// ...
// 找到錨點(具體過程等到分析 layout 時再說)
// (1)
detachAndScrapAttachedViews(recycler);
if (mAnchorInfo.mLayoutFromEnd) {
// ...
} else {
// (2)
fill(recycler, mLayoutState, state, false);
// ...
}
// ...
}
首先看(1)處,detachAndScrapAttachedViews 方法會根據情況将子 View 回收到相應緩存,具體過程之後再看,由于現在是第一次 layout,RecyclerView 中沒有子 View,是以現在該方法沒啥用。
接下來看(2)處,這裡的 fill 方法比較重要,它的作用是填充布局。看一下該方法:
LinearLayoutManager#fill
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// 進行 layout 時 layoutState.mScrollingOffset 的值被設定為
// LayoutState.SCROLLING_OFFSET_NaN,不會進入此 if 塊
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// ...
recycleByLayoutState(recycler, layoutState);
}
// 需要填充的空間
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
// 還有需要填充的空間并且 item 數未滿
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
// ...
// (1)
layoutChunk(recycler, state, layoutState, layoutChunkResult);
// 計算剩餘空間
// 同上,在 layout 時不會進入 if 塊中
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// ...
recycleByLayoutState(recycler, layoutState);
}
// ...
}
}
主要看(1)處的 layoutChunk 方法,隻要還有需要填充的空間,就會不斷調用該方法:
LinearLayoutManager#layoutChunk
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// (1)
View view = layoutState.next(recycler);
// ...
// 預設情況下,layoutState.mScrapList 等于 null
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
// (2)
addView(view);
} else {
addView(view, 0);
}
} else {
// ...
}
}
(2)處的 addView 方法就不多說了,該方法将得到的子 View 添加到 RecyclerView 中。主要看(1)處,看看子 View 從何而來:
View next(RecyclerView.Recycler recycler) {
// ...
final View view = recycler.getViewForPosition(mCurrentPosition);
return view;
}
這個方法是不是很熟悉呢?沒錯,它就是之前分析的 Recycler 的 getViewForPosition 方法。
不過由于現在沒有任何緩存,是以第一次 layout 的時候是通過 Adapter 的 createViewHolder 來建立子 View的,并且沒有添加任何緩存。
更新清單
更新清單可以使用 Adapter 的一系列 notify 方法,這裡分析其中兩個方法:notifyDataSetChanged 和 notifyItemChanged(int)。
Adapter#notifyDataSetChanged
該方法最終調用了 RecyclerViewDataObserver 的 onChanged 方法:
@Override
public void onChanged() {
// ...
// 該方法主要做了這兩件事
// 1. 給所有 ViewHolder 添加了 FLAG_UPDATE 和 FLAG_INVALID
// 2. 預設情況下(mHasStableIds 為 false)清空 CacheViews
processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
// 進行視圖重繪
requestLayout();
}
}
該方法會進行視圖重繪,又來到了 layout 過程,繼續以 LinearLayoutManager 為例,從它的 onLayoutChildren 方法看起,由于分析第一次 layout 時已經看過一遍了,這次主要看下不同之處:
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// ...
detachAndScrapAttachedViews(recycler);
// ...
}
主要差別在于 detachAndScrapAttachedViews 方法,這次它開始起作用了,該方法在 RecyclerView 的 LayoutManager 中定義,看下它的實作:
LayoutManager#detachAndScrapAttachedViews
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);
}
}
由于不是第一次 layout,RecyclerView 這時已經有子 View 了,該方法周遊子 View,調用 scrapOrRecycleView 方法:
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
// 不能回收添加了 FLAG_IGNORE 标記的 ViewHolder
// 可通過 LayoutManager 的 ignoreView 為相應的 View 添加該标記
if (viewHolder.shouldIgnore()) {
return;
}
// 這些條件都滿足,進入 if 塊
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
// ...
}
}
這裡将子 View 移除并通過 Recycler 的 recycleViewHolderInternal 方法進行回收:
Recycler#recycleViewHolderInternal
void recycleViewHolderInternal(ViewHolder holder) {
// ...
boolean cached = false;
boolean recycled = false;
if (forceRecycle || holder.isRecyclable()) {
// 由于此時的 ViewHolder 有 FLAG_INVALID 标記,不會進入此 if 塊
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
//...
}
// cached 仍為 false,進入此 if 塊
if (!cached) {
// 通過 RecycledViewPool 的 putRecycledView 方法緩存該 ViewHolder
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
// ...
}
最終被移除的子 View 緩存到了 RecycledViewPool 中。
後面在調用 fill 方法進行布局填充時,就可以從 RecycledViewPool 中拿取緩存的 View。
Adapter#notifyItemChanged
該方法傳入一個 int 參數,表示要資料有更新的 item 的 position。
public final void notifyItemChanged(int position) {
mObservable.notifyItemRangeChanged(position, 1);
}
最終調用 RecyclerViewDataObserver 的 onItemRangeChanged 方法:
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
// 會在 mAdapterHelper 中建立一個 UpdateOp,将資訊儲存起來
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
// 如果可以進行更新操作,執行該方法
triggerUpdateProcessor();
}
}
繼續看 triggerUpdateProcessor 方法:
void triggerUpdateProcessor() {
// 判斷條件預設為 false,執行 else 塊
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
// ...
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
在儲存了一些資訊後,還是進行視圖重繪。來到了 layout 過程後,還是以 LinearLayoutManager 為例,這次先看下布局過程的 step1,也就是 dispatchLayoutStep1 方法:
RecyclerView#dispatchLayoutStep1
private void dispatchLayoutStep1() {
// ...
processAdapterUpdatesAndSetAnimationFlags();
// ...
}
主要看 processAdapterUpdatesAndSetAnimationFlags 方法,從名字也可以看出,它負責更新 adapter 的資訊:
private void processAdapterUpdatesAndSetAnimationFlags() {
// ...
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}
// ...
}
這裡借助了 mAdapterHelper,它最終又通過接口回調(回調了 markViewHoldersUpdated 方法)調用了 RecyclerView 的 viewRangeUpdate 方法:
void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
// ...
for (int i = 0; i < childCount; i++) {
// ...
if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
// (1)
holder.addFlags(ViewHolder.FLAG_UPDATE);
// ...
}
}
}
該方法就是周遊所有子 View,找到所有發生了改變的子 View,進行相關操作。這裡重點看注釋(1),為改變的 ViewHolder 添加了 FLAG_UPDATE 标記。先記住這點,在後面會用到。
接下來看 onLayoutChildren 方法,和 notifyDataSetChanged 一樣,主要的不同之處也是在于 detachAndScrapAttachedViews 方法,該方法周遊子 View,調用 scrapOrRecycleView 方法,下面看一下該方法:
LayoutManager#scrapOrRecycleView
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
// ...
// 這次 ViewHolder 沒有添加 FLAG_INVALID 标記,進入 else 塊
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
// ...
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
這裡就和 notifyDataSetChanged 時不一樣了,由于在視圖重繪前沒有給 ViewHolder 添加 FLAG_INVALID 标記,這次進入的是 else 塊。
首先将 View 從 RecyclerView 中 detach 掉(而不是 remove 掉)。然後在回收時,調用的是 Recycler 的 scrapView 方法。該方法在前面也分析過了,這裡再看一次:
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
// 滿足這幾個條件中的一個就可以進入 if 循環
// 1. ViewHolder 設定了 FLAG_REMOVED 或 FLAG_INVALID
// 2. ViewHolder 沒有設定 FLAG_UPDATE
// 3. 沒有設定動畫或者動畫可以重用該 ViewHolder
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
// ...
mAttachedScrap.add(holder);
}
// 不滿足上述任意一個條件時,将 View 緩存到 mChangedScrap 中
else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
重點看判斷裡面的條件 2,從前面的分析可以得知,對于發生改變的 ViewHolder,給它設定了 FLAG_UPDATE,是以它現在三個條件都不滿足,進入 else 塊,而對于其他的 ViewHolder,由于沒有設定 FLAG_UPDATE,是以滿足條件 2,進入 if 循環。
是以通過 notifyItemChanged 方法更新清單時,發生了改變的子 View 将被緩存到 ChangedScrap 中,而沒有發生改變的子 View 則緩存到 AttachedScrap 中,之後通過填充布局的時候對于不同 item 就可以從相應的 Scrap 緩存中得到子 View。
另外,Scrap 緩存隻作用于布局階段,在 layout 的 step3 中将會清空 mAttachedScrap 和 mChangedScrap。
其實還有一個常見的場景是滑動操作,滑動出螢幕的子 View 将會緩存到 mCachedView,不過這裡就不詳細說了,在之後會在其他文章專門分析滑動這塊。
後記
本文圍繞 Recycler 展開叙述,重點是要通過它的幾個成員變量了解它的緩存機制,四級緩存分别是什麼,是在何時調用的,各自起到的作用,不同場景下使用哪種緩存等。
Recycler 和 LayoutManager 的布局以及動畫都有聯系,例如 LayoutManager 負責布局,它決定擷取子 View 和回收子 View 的時機,具體的工作就交由 Recycler 負責。這些會在之後對 RecyclerView 的其他方面作分析時進行更詳細的說明。
參考
- RecyclerView 源碼分析
- RecyclerView源碼分析
- RecyclerView的複用機制
- RecyclerView的緩存分析
- RecyclerView 源碼分析(三) - RecyclerView的緩存機制