Recycler類(lèi)是RecyclerView內(nèi)部final類(lèi),它管理scrapped(廢棄)或detached(獨(dú)立)的Item視圖缀蹄,使它們可以重用峭跳。我們都知道,在ListView中缺前,也有一個(gè)類(lèi)似的RecycleBin類(lèi)蛀醉,管理Item的重用。本文的重點(diǎn)是Recycler類(lèi)诡延,分析一下視圖在消失與出現(xiàn)時(shí)滞欠,如何利用Recycler實(shí)現(xiàn)重用。
ViewHolder類(lèi)RecyclerView的內(nèi)部抽象類(lèi)肆良,我們自己定義的Adapter中實(shí)現(xiàn)筛璧,封裝子視圖的一些視圖。
Scrapped視圖
先看一下Recycler內(nèi)部的幾個(gè)引用惹恃。
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
mAttachedScrap列表:用來(lái)存儲(chǔ)Scrapped(廢棄)的ViewHolder夭谤,它對(duì)應(yīng)的視圖是detached的,即ItemView調(diào)用了ViewGroup的detachViewFromParent方法巫糙,從容器的子視圖數(shù)組中移除朗儒,它其實(shí)并沒(méi)有被廢棄。它正是存放從RecyclerView中detached的ItemView的ViewHolder列表。
當(dāng)RecyclerView初始加載Item醉锄,第一次觸發(fā)onLayoutChildren時(shí)乏悄,fill創(chuàng)建滿足RecyclerView高度的子ItemView,ViewHolder綁定ItemView恳不,并ViewGroup#addView加入RecyclerView視圖檩小。第二次onLayoutChildren時(shí),通過(guò)detachAndScrapAttachedViews方法將全部ItemView從mChildren數(shù)組刪除烟勋,觸發(fā)的是ViewGroup#detachViewFromParent方法规求,ItemView變?yōu)閐etached,ViewHolder放入mAttachedScrap卵惦,fill繼續(xù)觸發(fā)從mAttachedScrap中獲取ViewHolder阻肿,將ViewHolder加入子View數(shù)組,觸發(fā)的是ViewGroup#attachViewToParent方法沮尿。
綜上所述:mAttachedScrap列表只是暫存從RecyclerView容器中detached下來(lái)的ItemView丛塌,也可以說(shuō)從mChildren數(shù)組移除的ItemView,這些ItemView屬于Scrapped蛹找,但是立馬又會(huì)被attach到RecyclerView姨伤。
mCachedViews列表:從RecyclerView區(qū)域移除,從ViewGroup中刪除的ItemView庸疾,存儲(chǔ)在列表中,最大值max当编,大于max時(shí)届慈,刪除最早進(jìn)入的第0個(gè)元素,該元素放入RecycledViewPool中忿偷,如果還是放不下金顿,直接放入RecycledViewPool。永遠(yuǎn)存儲(chǔ)最新從RecyclerView刪除的視圖ViewHolder鲤桥。
ViewGroup已經(jīng)執(zhí)行過(guò)removeViewAt刪除了View揍拆。
RecycledViewPool:視圖緩存池,當(dāng)mCachedViews存儲(chǔ)不下時(shí)茶凳,將ViewHolder放入嫂拴,根據(jù)類(lèi)型存儲(chǔ)。ViewGroup已經(jīng)執(zhí)行過(guò)removeViewAt刪除了View贮喧。
ViewCacheExtension:擴(kuò)展使用筒狠,開(kāi)發(fā)者自己控制緩存。
圖中的數(shù)據(jù)源一共有17項(xiàng)箱沦,顯示區(qū)域中辩恼,可容納的子視圖大約在12個(gè)左右。
ItemView視圖消失邏輯
RecyclerView視圖顯示出來(lái)以后,手指觸屏灶伊,向上滑動(dòng)疆前。此時(shí),position是0,1,2,3...的ItemView依次滾動(dòng)出視圖可見(jiàn)范圍聘萨。
通過(guò)源碼調(diào)試峡继,發(fā)現(xiàn)在LinearLayoutManager的recycleChildren方法處,觸發(fā)了下面的方法匈挖,定義在LayoutManager類(lèi)碾牌。
public void removeAndRecycleViewAt(int index, Recycler recycler) {
final View view = getChildAt(index);
removeViewAt(index);//從父容器中刪除。
recycler.recycleView(view);//存入Recycler
}
首先儡循,LayoutManager的removeViewAt方法舶吗,從RecyclerView中刪除索引index的子視圖,它與position無(wú)關(guān)择膝。調(diào)用輔助類(lèi)ChildHelper的removeViewAt方法誓琼。
public void removeViewAt(int index) {
final View child = getChildAt(index);
if (child != null) {
mChildHelper.removeViewAt(index);
}
}
RecyclerView類(lèi)的初始化initChildrenHelper方法,定義Callback對(duì)象肴捉,在輔助類(lèi)的方法中腹侣,調(diào)用內(nèi)部Callback的對(duì)應(yīng)方法。
private void initChildrenHelper() {
mChildHelper = new ChildHelper(new ChildHelper.Callback() {
...
@Override
public void removeViewAt(int index) {
final View child = RecyclerView.this.getChildAt(index);
if (child != null) {
dispatchChildDetached(child);
}
RecyclerView.this.removeViewAt(index);
}
...
});
}
dispatchChildDetached方法齿穗,通知子視圖detached傲隶,將調(diào)用Adapter的onViewDetachedFromWindow方法,可以在自己的Adapter中重寫(xiě)窃页。注意跺株,這里并沒(méi)有觸發(fā)ViewGroup的detachViewFromParent方法。
RecyclerView的removeViewAt方法脖卖,調(diào)用父類(lèi)ViewGroup的removeViewAt方法乒省,刪除該ItemView子視圖。
手指上滑畦木,每次最頂部Item視圖滑出屏幕時(shí)袖扛,刪除的都是index是0的子視圖,手指下移十籍,每次底部Item視圖滑出可視范圍草娜,刪除的都是index是12左右的子視圖矾睦,與position無(wú)關(guān)拐揭。
其次玄窝,調(diào)用Recycler的recycleView方法,將ViewHolder加入緩存mCachedViews或RecycledViewPool池械姻。
public void recycleView(View view) {
ViewHolder holder = getChildViewHolderInt(view);
if (holder.isTmpDetached()) {
removeDetachedView(view, false);
}
if (holder.isScrap()) {
holder.unScrap();
} else if (holder.wasReturnedFromScrap()){
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
根據(jù)View獲取它綁定的ViewHolder對(duì)象妒蛇,從View的LayoutParams中獲取机断。ViewHolder的內(nèi)部mScrapContainer(即Recycler)是空,isScrap方法返回false绣夺。只有執(zhí)行過(guò)Recycler的scrapView(View)方法吏奸,將ViewHolder加入到mAttachedScrap列表時(shí),才會(huì)設(shè)置內(nèi)部mScrapContainer值陶耍,當(dāng)isScrap返回true時(shí)奋蔚,調(diào)用unScrap方法,調(diào)用內(nèi)部Recycler的unscrapView方法烈钞。
void unscrapView(ViewHolder holder) {
if (holder.mInChangeScrap) {
mChangedScrap.remove(holder);
} else {
mAttachedScrap.remove(holder);
}
holder.mScrapContainer = null;
holder.mInChangeScrap = false;
holder.clearReturnedFromScrapFlag();
}
從mAttachedScrap列表中刪除泊碑,置空ViewHolder內(nèi)部Recycler。
Recycler的recycleViewHolderInternal方法毯欣,將ViewHolder加入緩存mCachedViews或RecycledViewPool池馒过。
void recycleViewHolderInternal(ViewHolder holder) {
...
final boolean transientStatePreventsRecycling = holder
.doesTransientStatePreventRecycling();
final boolean forceRecycle = mAdapter != null
&& transientStatePreventsRecycling
&& mAdapter.onFailedToRecycleView(holder);
boolean cached = false;
boolean recycled = false;
if (forceRecycle || holder.isRecyclable()) {
if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE)) {
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
//刪除mCachedViews第0個(gè)元素,并觸發(fā)
//addViewHolderToRecycledViewPool方法加入RecycledViewPool
recycleCachedViewAt(0);
cachedViewSize --;
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {//未加入mCachedViews時(shí)
addViewHolderToRecycledViewPool(holder);
recycled = true;
}
} else if (DEBUG) {
}
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
待加入的ViewHolder不能是Scrap酗钞,前面經(jīng)過(guò)unScrap方法處理過(guò)腹忽。緩存mCachedViews最大值是mViewCacheMax,當(dāng)達(dá)到最大時(shí)砚作,刪除第一個(gè)窘奏,被刪除元素加入RecycledViewPool。如果數(shù)量已經(jīng)小于最大值葫录,將新ViewHolder放入mCachedViews緩存着裹,如果仍然大于,將其放入RecycledViewPool压昼。
void addViewHolderToRecycledViewPool(ViewHolder holder) {
ViewCompat.setAccessibilityDelegate(holder.itemView, null);
dispatchViewRecycled(holder);//派發(fā)回調(diào)
holder.mOwnerRecyclerView = null;
getRecycledViewPool().putRecycledView(holder);//入池
}
將ViewHolder所屬的RecyclerView置空求冷,執(zhí)行dispatchViewRecycled回調(diào),該方法將調(diào)用Adapter的onViewRecycled方法窍霞,可重寫(xiě)。ViewHolder放置到RecycledViewPool緩存池拯坟。
綜上所述
當(dāng)position是0的視圖移除屏幕但金,將ViewHolder存入mCachedViews緩存,最大緩存默認(rèn)是2郁季,當(dāng)position是1的視圖移除屏幕冷溃,也會(huì)存入mCachedViews緩存。當(dāng)position是2的視圖移除屏幕梦裂,將緩存中的第一個(gè)ViewHolder元素刪除似枕,加入RecycledViewPool池。position是2的視圖ViewHolder存入緩存年柠。這是視圖消失的基本邏輯凿歼。
ItemView視圖出現(xiàn)的邏輯
手指觸屏,向上滑動(dòng),position是12,13,14,15...的ItemView依次從底部冒出答憔,通過(guò)調(diào)試源碼味赃,調(diào)用Recycler的getViewForPosition方法。該方法根據(jù)position獲取ItemView視圖虐拓,position是RecyclerView的數(shù)據(jù)源索引心俗,當(dāng)視圖完全展示后,子視圖有12個(gè)蓉驹,那么城榛,最后一個(gè)的索引是11,position是12索引對(duì)應(yīng)視圖不可見(jiàn)态兴,上滑時(shí)狠持,12索引首先出現(xiàn)。
View getViewForPosition(int position, boolean dryRun) {
/**position邊界判斷**/
boolean fromScrap = false;
ViewHolder holder = null;
if (holder == null) {
//根據(jù)position從ScrapView中獲取holder
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
if (holder != null) {
//驗(yàn)證holder是否可用于position位置
if (!validateViewHolderForOffsetPosition(holder)) {
...
holder = null;
} else {
fromScrap = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
//拋出邊界溢出異常IndexOutOfBoundsException
}
final int type = mAdapter.getItemViewType(offsetPosition);
//通過(guò)stable ids查找Scrap
...
if (holder == null) {
//從RecycledViewPool獲取
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();//這里會(huì)設(shè)置mPosition=-1
}
}
if (holder == null) {
//Adapter創(chuàng)建holder
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
...
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
} else if (!holder.isBound() || holder.needsUpdate() ||
holder.isInvalid()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
holder.mOwnerRecyclerView = RecyclerView.this;
mAdapter.bindViewHolder(holder, offsetPosition);
bound = true;
...
}
...
return holder.itemView;
}
首先诗茎,從mAttachedScrap與mCachedViews中查找ViewHolder工坊,在視圖滾動(dòng)時(shí),mAttachedScrap是空的敢订,因此王污,一般情況從mCachedViews緩存查找。
validateViewHolderForOffsetPosition方法楚午,驗(yàn)證holder是否可用于對(duì)應(yīng)position索引昭齐。如果驗(yàn)證通過(guò),設(shè)置fromScrap標(biāo)志矾柜,返回holder的itemView視圖阱驾。如果驗(yàn)證失敗,將增加無(wú)效標(biāo)志怪蔑,holder內(nèi)部mScrapContainer(即Recycler)存在里覆,說(shuō)明holder是isScrap的 ,Scrap的holder無(wú)法被回收缆瓣,unScrap方法提前去除其標(biāo)志喧枷,最后會(huì)加入緩存,recycleViewHolderInternal方法弓坞。
其次隧甚,從RecycledViewPool緩存池中查找。從這里獲取的ViewHolder渡冻,設(shè)置mPosition是NO_POSITION(-1)戚扳。如果都未找到,通過(guò)Adapter的createViewHolder方法創(chuàng)建族吻,調(diào)用Adapter的onCreateViewHolder抽象方法帽借,開(kāi)發(fā)者重寫(xiě)此方法珠增,初始化ItemView,創(chuàng)建ViewHolder對(duì)象宜雀。最后切平,通過(guò)Adapter的bindViewHolder方法,調(diào)用Adapter的onBindViewHolder抽象方法辐董,開(kāi)發(fā)者重寫(xiě)此方法悴品。初始化ViewHolder的View中數(shù)據(jù)。
Recycler的getScrapViewForPosition方法简烘。
ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
//從mAttachedScrap查找苔严,視圖初始顯示時(shí)走這一步
//滾動(dòng)時(shí)不會(huì)走這里。
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())) {
if (type != INVALID_TYPE && holder.getItemViewType() != type) {
//ViewType不同
break;
}
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
...
//從mCachedViews列表查找
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
//無(wú)效標(biāo)志FLAG_INVALID的holder可能存在與cache中孤澎。
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
}
}
return null;
}
當(dāng)視圖滾動(dòng)時(shí)届氢,該方法從緩存mCachedViews查找ViewHolder,并且它的mPosition要和position一致覆旭。
舉個(gè)例子說(shuō)明一下退子。假如position是12完全不可見(jiàn),當(dāng)向上滑動(dòng)時(shí)型将,position是12的視圖出現(xiàn)寂祥,此時(shí),ViewHolder不是getScrapViewForPosition獲取七兜。因?yàn)閙CachedViews還是空丸凭,或者position是0的視圖已在mCachedViews緩存,但它的mPosition是0腕铸,與12不相等惜犀,也不會(huì)使用它。因此狠裹,position是12的要新建ViewHolder虽界。當(dāng)position是13和14...視圖出現(xiàn),對(duì)應(yīng)position是1,2..的視圖要進(jìn)入mCachedViews緩存涛菠,如果mCachedViews緩存未達(dá)到最大值浓恳,將會(huì)一直新建ViewHolder,原因也一樣碗暗,mPosition不符合。如果到達(dá)最大值梢夯,緩存的最大值默認(rèn)是2言疗,此時(shí),已經(jīng)存儲(chǔ)position是0和1的值颂砸,繼續(xù)上滑噪奄,position是2的視圖要進(jìn)緩存死姚,刪掉最早position是0的值,將它放入RecycledViewPool池勤篮。繼續(xù)都毒,position是14的出現(xiàn),從緩存未找到符合的position碰缔,因?yàn)榇丝叹彺胬镞€都是頭部position較小的值账劲,RecycledViewPool已經(jīng)有值,就從RecycledViewPool獲取金抡。這里獲取的與positon無(wú)關(guān)瀑焦,ViewHoder的mPosition都是-1,只要type類(lèi)型一樣梗肝,在Adapter的bindViewHolder方法榛瓮,會(huì)為mPosition賦值,這個(gè)ViewHolder內(nèi)部mPosition就屬于14啦巫击。
改變方向手指下滑禀晓,position是2的視圖出屏幕,對(duì)應(yīng)的ViewHolder在緩存坝锰,直接使用粹懒。position是14的消失了,將position是14的ViewHolder加入緩存什黑。
綜上所述
緩存mCachedViews崎淳,存儲(chǔ)的總是最新消失Item視圖對(duì)應(yīng)的ViewHolder,ype != INVALID_TYPE && holder.getItemVie不管它是在頂部消失愕把,還是在底部消失拣凹。它的最大值也不宜過(guò)大,設(shè)計(jì)過(guò)大的話會(huì)就可以一直裝入恨豁,未出現(xiàn)過(guò)的position都要新建ViewHolder嚣镜。比如,緩存無(wú)限大橘蜜,一屏顯示11個(gè)菊匿,上滑,這11個(gè)都可以進(jìn)入緩存计福,那么后面出來(lái)11個(gè)左右都因position不符而新建跌捆。再下滑,后面出來(lái)的這些也可以進(jìn)入緩存象颖,從緩存取出上面的一批顯示佩厚,這就用不到RecycledViewPool了,失去了它原有的功能说订。
那么抄瓦,為什么會(huì)有mCachedViews呢潮瓶?
如果直接在RecycledViewPool池存儲(chǔ),當(dāng)?shù)撞恳晥D出來(lái)就可以重用第一個(gè)消失的視圖钙姊。對(duì)于在一個(gè)位置不停上下滑動(dòng)時(shí)毯辅,個(gè)人感覺(jué),從mCachedViews查找更快一些煞额。
到這里思恐,我們已經(jīng)獲取了屏幕下一個(gè)將要顯示的ItemView,接下來(lái)就要將它加入到RecyclerView視圖中立镶,調(diào)用LayoutManager#addViewInt方法壁袄。
private void addViewInt(View child, int index, boolean disappearing) {
final ViewHolder holder = getChildViewHolderInt(child);
...
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (holder.wasReturnedFromScrap() || holder.isScrap()) {
//視圖剛展現(xiàn)時(shí),從mAttachedScrap獲取數(shù)據(jù)時(shí)觸發(fā)這里媚媒。
if (holder.isScrap()) {
holder.unScrap();
} else {
holder.clearReturnedFromScrapFlag();
}
mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
if (DISPATCH_TEMP_DETACH) {
ViewCompat.dispatchFinishTemporaryDetach(child);
}
} else if (child.getParent() == mRecyclerView) {
int currentIndex = mChildHelper.indexOfChild(child);
if (index == -1) {
index = mChildHelper.getChildCount();
}
if (currentIndex == -1) {
//拋出異常
}
if (currentIndex != index) {
mRecyclerView.mLayout.moveView(currentIndex, index);
}
} else {
mChildHelper.addView(child, index, false);
lp.mInsetsDirty = true;
if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
mSmoothScroller.onChildAttachedToWindow(child);
}
}
....
}
如果發(fā)現(xiàn)ViewHolder的FLAG_RETURNED_FROM_SCRAP標(biāo)志或isScrap嗜逻,先unScrap處理,再調(diào)用ViewGroup的attachViewToParent方法缭召。在滾動(dòng)時(shí)栈顷,獲取的isScrap是false。
借助ChildHelper的addView方法嵌巷,調(diào)用CallBack的addView方法萄凤,最終,調(diào)用的是ViewGroup的addView搪哪,ItemView加入父容器靡努,dispatchChildAttached方法,會(huì)觸發(fā)Adapter的onViewAttachedToWindow方法晓折。
@Override
public void addView(View child, int index) {
RecyclerView.this.addView(child, index);
dispatchChildAttached(child);
}
綜上所述
當(dāng)視圖進(jìn)入可視范圍惑朦,從緩存mCachedViews或RecycledViewPool獲取ViewHolder,獲取內(nèi)部ItemView漓概,ViewGroup的addView方法將視圖加入父視圖漾月。
這是視圖可視加/取的邏輯。
ChildHelper輔助類(lèi)
ItemView幫助類(lèi)胃珍,它通過(guò)內(nèi)部Callback接口暴露出來(lái)梁肿,在RecyclerView類(lèi)初始化ChildHelper時(shí)實(shí)現(xiàn)接口方法,調(diào)用RecyclerView的對(duì)應(yīng)方法觅彰。處理子視圖會(huì)借助父類(lèi)ViewGroup吩蔑。
任重而道遠(yuǎn)