大家元旦快樂~
好記性不如爛筆頭,所以我準備弄個源碼解析系列渐夸,不準備詳細解析源碼嗓蘑,但把基本原理和設(shè)計思想梳理清楚,也給自己留個筆記存檔好在后面需要的時候翻起虫溜。
今天就從ListView開始。
ListView的核心在于layoutChildren函數(shù)股缸,分兩種情況吼渡,一種是全新加載,第二種是非全新加載乓序。主要區(qū)別在于ListView內(nèi)部的View緩存池的使用寺酪,下面依次來講下坎背。
在layoutChildren里面,會根據(jù)LayoutMode選擇調(diào)用fillSpecific/fillUp/fillFromTop之類的函數(shù)來進行填充寄雀,這個只是策略問題不是很關(guān)鍵得滤,最終這些函數(shù)都調(diào)用了makeAndAddView,然后再調(diào)用obtainView盒犹,再調(diào)用adapter.getView懂更,拿到view之后再使用setupChild加入到ListView里面去并放好位置(包含child自己的measure),流程如下:
setupChild函數(shù)里面片段:
全新加載的很好理解急膀,每個Item都是按照上面的流程走沮协,并且每個Item的view都是通過getView里面inflate出來的(這種情況getView的convertView傳過來是空,意味著ListView還沒有緩存view可以使用)
非全新加載卓嫂,比如頁面滑動慷暂,或者adapter數(shù)據(jù)發(fā)生變化,這種情況下面整體流程和全新加載沒有區(qū)別晨雳,但在部分函數(shù)調(diào)用里面有細微差別行瑞,比如:
layoutChildren里面首先將ListView的child都放入緩存池:
// Pull all children into the RecycleBin.
// These views will be reused if possible
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycler;
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
}else {
recycleBin.fillActiveViews(childCount, firstPosition);
}
緩存池管理就是這個RecycleBin對象,他里面有兩種緩存餐禁,一種叫ActiveViews血久,看上面代碼如果不是數(shù)據(jù)發(fā)生變化的非全新加載(比如頁面滾動),則把所有子view都放入ActiveViews帮非,然后重新計算位置重新擺放新的view的時候氧吐,就會首先從ActiveViews里面拿出緩存view,看makeAndAddView函數(shù)的第一段就是:
更巧妙的是末盔,在拿ActiveView的緩存view的時候副砍,會根據(jù)位置來拿,這樣的view認為是不需要重新經(jīng)過adapter的getView函數(shù)的庄岖,這樣極大的提高了效率。(頁面滾動的時候頁面里面的item只是位置變化角骤,不需要重新調(diào)用adapter.getView函數(shù))
View getActiveView(int position) {
int index = position -mFirstActivePosition;
final View[] activeViews =mActiveViews;
if (index >=0 && index < activeViews.length) {
final View match = activeViews[index];
activeViews[index] =null;
return match;
}
return null;
}
假如ActiveViews里面拿不到緩存view了隅忿,比如ListView高度發(fā)生了變化,需要更多的view來填充邦尊,這個時候就會從另外一種緩存里面拿背桐,叫做ScrapViews。這種緩存view是屬于ListView被填滿了蝉揍,結(jié)果還剩余有view就會被放入到這個緩存池里面來链峭。在layoutChildren函數(shù)的尾部可以看到有這樣一段代碼就是這個意思:
// Flush any cached views that did not get reused above
recycleBin.scrapActiveViews();
/**
* Move all views remaining in mActiveViews to mScrapViews.
*/
void scrapActiveViews() {
final View[] activeViews =mActiveViews;
final boolean hasListener =mRecyclerListener !=null;
final boolean multipleScraps =mViewTypeCount > 1;
ArrayList scrapViews =mCurrentScrap;
final int count = activeViews.length;
for (int i = count - 1; i >= 0; i--) {
.......
從ScrapViews拿緩存view的代碼在obtainView里面:
final View scrapView =mRecycler.getScrapView(position);
final View child =mAdapter.getView(position, scrapView,this);
if (scrapView !=null) {
if (child != scrapView) {
// Failed to re-bind the data, return scrap to the heap.
mRecycler.addScrapView(scrapView, position);
}else if (child.isTemporarilyDetached()) {
outMetadata[0] =true;
// Finish the temporary detach started in addScrapView().
child.dispatchFinishTemporaryDetach();
}
}
可以看到adapter.getView的第二個參數(shù)convertView就是從ScrapViews里面拿過來的緩存view,可能為空也可能不為空又沾,所以我們的getView函數(shù)都要有null判斷弊仪。
這樣子我們就能串起來了熙卡,在makeAndAddView里面先看看ActiveViews里面有沒有緩存view,有的話則直接成功返回也不需要調(diào)用adapter.getView励饵。沒有的話則繼續(xù)調(diào)用obtainView驳癌,然后再去ScrapViews里面看看有沒有緩存view,ScrapViews里面拿出來的view都需要重新經(jīng)過adapter.getView進行重新刷數(shù)據(jù)役听。ScrapViews可能拿到緩存view也可能拿不到颓鲜,所以getView實現(xiàn)需要null判斷。
我們可以把ActiveViews和ScrapViews理解為一二級緩存典予,有效率上面的差別(很明顯后者要經(jīng)過getView重新綁定數(shù)據(jù))
ListView的layoutChildren在開始的時候通過fillActiveViews將子view全部放到ActiveViews里面甜滨,然后等會重新布局的時候又首先從ActiveViews里面拿出來,不夠用的時候再繼續(xù)從ScrapViews里面拿瘤袖,非常高效衣摩。所以ActiveViews可以理解為layout期間的一個臨時產(chǎn)物。
整體流程就結(jié)束了孽椰,下面介紹下有些細節(jié)的地方可以從中學(xué)習(xí)到的昭娩。
View有onAttachedToWindow/onDetachFromWindow,還有一個不怎么常用的onStartTemporaryDetach/onFinishTemporaryDetach黍匾,表示開始和結(jié)束臨時Detach栏渺,這種狀態(tài)View其實沒有真正觸發(fā)onDetachFromWindow,只是臨時的Detached了锐涯,在addScrapView函數(shù)里看到有調(diào)用start磕诊,view被放入ScrapViews緩存池了,臨時被detach:
然后在obtainView里面纹腌,從ScrapViews里面重新拿出來要使用了霎终,再調(diào)用finish:
這個臨時detached挺有意思,結(jié)合ViewGroup的detachAllViewsFromParent(在layoutChildren里面一開始就會調(diào)用這個方法)升薯,讓子view的parent都設(shè)成null莱褒,可以達到一些很巧妙的實現(xiàn)。
ScrapViews里面的緩存view有的是真正onDetachFromWindow涎劈,有的則是onStartTemporaryDetach广凸,造成這個的原因主要是scrapActiveViews和addScrapView兩個實現(xiàn)上的差異,不過這個不影響實際使用蛛枚,obtainView里面會根據(jù)實際情況來決定拿到的緩存view是要重新attachToWindow還是finshTemporaryDetach谅海,這個outMetData[0](和mIsScrap[0]是同一個)就是標(biāo)記緩存view是不是之前已經(jīng)detachFromWiindow(之前是isTemporarilyDetached的,說明還未真正detachFromWindow)
然后setupChild里面會根據(jù)是不是attachedToWindow做不同的操作:重新指定parent(attachViewToParent)還是重新attachToWindow(addViewInLayout)
源碼解析就怕別人看不懂蹦浦,但愿對同學(xué)們有些幫助~
更多文章關(guān)注微信公眾號:安卓之美