RecyclerView 是一個好用又複雜的控件,其功能的高度解耦化,規範化的 ViewHolder 寫法,以及對動畫的友好支持,都是它與傳統 ListView 的區別。html
它有幾大模塊:java
下面就從源碼(API 28)角度分析 RecyclerView,RecyclerView 的源碼很複雜,很難在一篇文章內講完,因此打算分幾篇來說,本文是第一篇,將圍繞 RecyclerView 的內部類 Recycler 展開分析:android
首先看一下它的做用,源碼上是這樣寫的:緩存
A Recycler is responsible for managing scrapped or detached item views for reuse.app
意思就是 Recycler 負責管理廢棄或被 detached 的 item 視圖,以便重複利用。ide
它有如下幾個成員變量:源碼分析
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
複製代碼
這幾個成員變量都和 RecyclerView 的緩存相關,若是按照四級緩存的話,它們能夠這樣劃分:佈局
第一級緩存:mAttachedScrap、mChangedScrappost
第二級緩存:mCachedViews動畫
第三級緩存:ViewCacheExtension
第四級緩存:RecycledViewPool
後面再介紹 mAttachedScrap、mChangedScrap、mCachedViews 具體存的是哪些 ViewHolder。
如今先了解下 RecycledViewPool 和 ViewCacheExtension這兩個類:
繼續先看官方註釋:
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 集合。
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。
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 容量,超出容量時,從後面開始刪除,直到知足新的容量。
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。
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 是一個由開發者控制的 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 方法比較重要,用於獲取某個位置須要展現的 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 的過程:
另外,光有視圖還不夠,還要爲視圖設置數據,因此後面還有這樣一段代碼:
// ViewHolder 還未進行綁定操做、帶有 FLAG_UPDATE 或 FLAG_INVALID 標記
else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
// ...
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
複製代碼
tryBindViewHolderByDeadline 方法最後會調用 Adapter 的 onBindViewHolder 方法,咱們平時就是在該方法上給視圖設置數據的。
既然叫 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 中。
從前面能夠得知,在執行 Recycler 的 recycleView 方法時,會將回收的 View 緩存到 mCahceViews 或 recycledViewPool 中,那麼另外兩個 Scrap 緩存(mChangedScrap 和 mAttachedScrap)中的 View 是什麼時候添加進來的呢?
不管是 mAttachedScrap 仍是 mChangedScrap ,它們得到 View 的途徑都只有一個,那就是經過 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。
那麼該方法在什麼時候調用呢?有兩種狀況:
detachAndScrapAttachedViews(recycler);
複製代碼
該方法定義在 RecyclerView 的 LayoutManager 中,它繼續調用 scrapOrRecycleView 方法,若是在該方法符合條件就調用 Recycler 的 scrapView 方法。
下面就根據一些場景來分析下 Recycler 是如何進行回收和複用的。
因爲這裏不是專門分析 layout 過程的,就不從 onLayout 開始說了,中間的過程省略掉,它最終會調用到 LayoutManager 的 onLayoutChildren,這裏以 LinearLayoutManager 爲例:
@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 方法比較重要,它的做用是填充佈局。看一下該方法:
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 方法,只要還有須要填充的空間,就會不斷調用該方法:
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)。
該方法最終調用了 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 中定義,看下它的實現:
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 方法進行回收:
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。
該方法傳入一個 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 方法:
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 方法,下面看一下該方法:
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 的其餘方面做分析時進行更詳細的說明。