環(huán)境
android sdk版本: 30
依賴:
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.recyclerview:recyclerview:1.2.1"
案例分析:
RecyclerView
寬高固定铲汪;LayoutManager
是LienarLayoutManager
熊尉,vertical方向;數(shù)據(jù)20條掌腰,足以鋪滿整個屏幕狰住。
現(xiàn)象:
①先創(chuàng)建Adapter
,設置20條數(shù)據(jù)齿梁。
②調(diào)用RecyclerView#Adapter#notifyDataSetChanged
方法后催植,當前頁面中只有5個ViewHolder
復用,其余的ViewHolder
會走Adapter#createViewHolder
方法創(chuàng)建新的ViewHolder
勺择。
原理:
為了搞清楚原理创南,我們先看一下,剛進入頁面時省核,RecyclerView#Adapter#onCreateViewHolder
方法的調(diào)用棧稿辙。
RecyclerView#Adapter#onCreateViewHolder方法的調(diào)用棧
RecyclerView#onLayout(): 4578行
RecyclerView#dispatchLayout(): 4012行
RecyclerView#dispatchLayoutStep2():4309行
LinearLayoutManager#onLayoutChildren(): 668行
LinearLayoutManager#fill(): 1591行
LinearLayoutManager#layoutChunk(): 1631行
LinearLayoutManager#LayoutState#next(): 2330行
RecyclerView#Recycler#getViewForPosition(int position): 6296行
RecyclerView#Recycler#getViewForPosition(position, boolean dryRun): 6300行
RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs): 6416行
RecyclerView#Adapter#createViewHolder(@NonNull ViewGroup parent, int viewType): 7295行
RecyclerView#Adapter#onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
其核心是RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline
方法,它主要有兩個作用气忠,一個是獲取ViewHolder
邻储;另一個給ViewHolder
綁定數(shù)據(jù)赋咽。
獲取ViewHolder是有順序的,會先嘗試從各級緩存里面去獲取吨娜,會依次從Recycler scrap
脓匿、cache
、RecycledViewPool
中獲取宦赠,如果都獲取不到陪毡,就直接創(chuàng)建一個ViewHolder
。
RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline
Attempts to get the ViewHolder for the given position, either from the Recycler scrap, cache, the RecycledViewPool, or creating it directly.
獲取給定位置的ViewHolder勾扭。會依次從Recycler scrap毡琉、cache、RecycledViewPool中獲取尺借,如果都獲取不到绊起,就直接創(chuàng)建一個ViewHolder
核心:獲取viewHolder;給viewHolder綁定數(shù)據(jù)燎斩。
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
// 如果需要預先布局虱歪,就嘗試從mChangedScrap中去獲取。
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
...
}
// 1) Find by position from scrap/hidden list/cache
// 嘗試依次從mAttachedScrap栅表、mChildHelper的mHiddenViews笋鄙、mCachedViews中去獲取可復用的ViewHolder
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
// 校驗holder是否有效,無效就清除vh
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
...
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
...
// 獲取這個位置對應的數(shù)據(jù)類型怪瓶,通過重寫的Adapter#getItemViewType方法萧落。
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
// 如果設置了stable ids,就根據(jù)id依次從mAttachedScrap洗贰、mCachedViews中查找
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
...
}
// 嘗試從mViewCacheExtension中獲取VH
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
...
}
}
// 嘗試從 RecycledViewPool 中獲取
if (holder == null) { // fallback to pool
...
holder = getRecycledViewPool().getRecycledView(type);
...
}
// 調(diào)用RecyclerView#Adapter#onCreateViewHolder生成ViewHolder
if (holder == null) {
...
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
}
...
// 給viewholder綁定數(shù)據(jù)
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
...
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
...
// 給viewholder綁定數(shù)據(jù)
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
// 給holder.itemView設置RecyclerView#LayoutParams找岖。并將其相互綁定。
...
return holder;
}
實例分析敛滋。
我們這里調(diào)用RecyclerView#Adapter#notifyDataSetChanged
方法后许布,既有復用的ViewHolder
,也有新建的ViewHolder
绎晃。復用的ViewHolder
來自于哪里蜜唾?為什么是5個
?為什么還要新建ViewHolder
庶艾?
帶著這些問題袁余,我們debug下我們的場景,看下ViewHolder
的來源咱揍。
核心在于調(diào)用RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline
方法颖榜,關鍵在于下面這段代碼:
holder = getRecycledViewPool().getRecycledView(type);
我們知道,RecycledViewPool
中是以viewType
來存放不同的ViewHolder
的,每個type
最多存放五個朱转。
所以我們在RecyclerView#Recycler#tryGetViewHolderForPositionByDeadline
中蟹地,從RecycledViewPool
中最多能找到五個可復用的ViewHolder
积暖,其余的只能走新建ViewHolder
流程了藤为。
RecycledViewPool
先來看下RecycledViewPool
的說明:
RecycledViewPool lets you share Views between multiple RecyclerViews.
If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool and use setRecycledViewPool(RecyclerView.RecycledViewPool).
RecyclerView automatically creates a pool for itself if you don't provide one.
大意是RecycledViewPool
可以讓你在多個recyclerview
之間共享視圖。
如果你想在RecyclerViews
中回收視圖夺刑,可以創(chuàng)建一個RecycledViewPool
的實例并使用setRecycledViewPool(RecyclerView.RecycledViewPool)
缅疟。
如果你不提供一個RecycledViewPool
實例,那么RecyclerView
會自動為自己創(chuàng)建一個遍愿。
我們看下RecyclerView#Recycler#getRecycledViewPool
方法:確實是自動創(chuàng)建了一個存淫。
RecycledViewPool getRecycledViewPool() {
if (mRecyclerPool == null) {
mRecyclerPool = new RecycledViewPool();
}
return mRecyclerPool;
}
再看下RecycledViewPool
的結構:
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
...
}
如上所示,里面有一個SparseArray<ScrapData>
類型的變量mScrap
沼填,用來存儲不同類型的ViewHolder
桅咆。ScrapData
數(shù)據(jù)中包含ArrayList<ViewHolder>
類型變量mScrapHeap
,用來存放具體的ViewHolder
坞笙,它的最大容量是5(DEFAULT_MAX_SCRAP
)岩饼。
RecycledViewPool中的數(shù)據(jù)何時添加的
本例中RecycledViewPool
中的數(shù)據(jù)是從哪里添加的呢?
本例中薛夜,向ScrapData#mScrapHeap
添加ViewHolder
數(shù)據(jù)的調(diào)用鏈如下:
RecyclerView#onLayout(): 4578行
RecyclerView#dispatchLayout(): 4012行
RecyclerView#dispatchLayoutStep2():4309行
LinearLayoutManager#onLayoutChildren(): 633行
RecyclerView#LayoutManager#detachAndScrapAttachedViews(): 9493行
RecyclerView#LayoutManager#scrapOrRecycleView(): 9508行
RecyclerView#Recycler#recycleViewHolderInternal(): 6671行
RecyclerView#Recycler#addViewHolderToRecycledViewPool(): 6723行
RecyclerView#RecyclerViewPool#putRecycledView(ViewHolder scrap): 5931行
scrapHeap.add(scrap);
核心是LinearLayoutManager#onLayoutChildren()
方法中籍茧,如下的這段代碼:
detachAndScrapAttachedViews(recycler);
也就是說,在調(diào)用RecyclerView#Adapter#notifyDataSetChanged
方法后梯澜,會觸發(fā)繪制流程寞冯。在Linearlayout#layoutChildren
方法中,會先對ViewHolder
進行緩存晚伙,然后會對ViewHolder
進行復用吮龄。