??最近在學(xué)習(xí)了一個RecyclerView新的組件--ConcatAdapter
,今天打算寫一篇文章來學(xué)習(xí)一下它的源碼實現(xiàn)铅歼。在這之前,我就學(xué)習(xí)過ConcatAdapter的用法换可,但是當(dāng)時只是僅限于用法椎椰,并沒有深入理解它的內(nèi)部實現(xiàn)成洗,所以不免有些遺憾档叔。今天婆跑,我們就來學(xué)習(xí)一下痴鳄。
??注意家浇,本文ConcatAdapter相關(guān)源碼均來自于1.2.0-alpha05版本
??本文參考資料:
1.概述
??ConcatAdapter
是RecyclerView是在1.2.0版本上推出的新組件背犯,在alpha04版本之前它叫MergeAdapter
玻墅,但是從alpha04
版本開始装诡,Google爸爸就將其改名為ConcatAdapter
吞歼。所以圈膏,可能有些同學(xué)對此有些陌生,但是ConcatAdapter
實際上就是之前的MergeAdapter
篙骡,用法和實現(xiàn)都是差不多的稽坤。
??相信大家在開發(fā)過程中都會遇到一種情況:RecyclerView
在使用Adapter加載數(shù)據(jù)的時候,可能會區(qū)分多種ViewType糯俗,一般的處理方式都是通過重新Adapter
的getItemViewType
方法返回不同的ViewType尿褪,然后在onCreateViewHolder
方法里面通過不同的ViewType來定義不同的布局。從一個具體的場景來說叶骨,RecyclerView通常會被分為三個部分茫多,分別是:Header部分,Content部分忽刽,F(xiàn)ooter部分天揖,這其中三個部分的布局均不相同夺欲,所以就需要通過不同的ViewType來實現(xiàn)目的。
??不過今膊,一般項目里面會通過這種場景的邏輯均是通用的些阅,所以我們最好是能將上面的邏輯定義成通用的,這樣復(fù)用起來就非常的方便斑唬。將這部分的邏輯定義在基類Adapter里面也是可以的市埋,但是正所謂繼承的方式?jīng)]有組合的方式好,所以如果能通過組合的方式實現(xiàn)Adapter的ViewType的拆分便是極好的恕刘。而ConcatAdapter
便可以將幾個Adapter組合成為一個Adapter缤谎,每個子Adapter里面的ViewType是相同,子Adapter之間的ViewType可以是不同的褐着,這樣便能將不同的邏輯拆分坷澡,后續(xù)在復(fù)用起來就會更加的方便。
??回到上面提到的具體場景含蓉,因為RecyclerView分為三個部分频敛,所以我們也可以將Adapter拆分三個部分,不同的部分使用不同的Adapter馅扣,然后通過ConcatAdapter
組合起來斟赚。
??廢話說的比較多,回到今天的主題差油,我們今天主要是介紹ConcatAdapter
的基本使用和實現(xiàn)原理拗军,主要是從如下幾個方面來介紹ConcatAdapter
:
- ConcatAdapter的基本使用。
- ConcatAdapter的基本架構(gòu)厌殉。
- ConcatAdapter針對
ViewType
的處理食绿。- ConcatAdaprer針對
stableId
的處理。
2. ConcatAdapter的基本使用
??在正式分析ConcatAdapter源碼之前公罕,我們先來了解一下ConcatAdapter的基本使用器紧。我們就按照上面的Header、Content和Footer三個部分的場景楼眷,實現(xiàn)一個小小的Demo铲汪,具體的效果如下圖:
??首先,我們要先定義三個Adapter罐柳,用來加載不同部分的布局掌腰,假設(shè)分別稱為:HeaderAdapter、ContentAdapter 和FooterAdapter张吉。這里就不詳細(xì)展開具體的實現(xiàn)齿梁,都是比較簡單的邏輯。
?定義好三個Adapter之后,接下來就是使用ConcatAdapter將三個Adapter組合起來勺择。組合步驟主要分為如下2步:
- 定義ConcatAdapter的Config创南。主要是配置ViewType是否相互隔離,以及stableId的策略省核。
- 使用ConcatAdapter將子Adapter組合起來稿辙。
??我們來看一下具體的代碼:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1. 定義Config
val config = ConcatAdapter.Config.Builder()
.setIsolateViewTypes(true)
.setStableIdMode(ConcatAdapter.Config.StableIdMode.SHARED_STABLE_IDS)
.build()
val adapter = ConcatAdapter(config)
// 2. 使用ConcatAdapter將三個Adapter組合起來。
adapter.addAdapter(HeaderAdapter(generateList("Header", 2)).apply { setHasStableIds(true)})
adapter.addAdapter(ContentAdapter(generateList("Content", 2)).apply { setHasStableIds(true)})
adapter.addAdapter(FooterAdapter(generateList("Footer", 2)).apply { setHasStableIds(true) })
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
}
private fun generateList(title: String, count: Int) = ArrayList<String>().apply {
for (index in 0 until count) {
add("$title position = $index")
}
}
}
??ConcatAdapter
的基本使用過程就是如上所說的內(nèi)容气忠,但是其中還有很多隱藏細(xì)節(jié)我并沒有邻储,比如說在定義Config的時候,setIsolateViewTypes
和setStableIdMode
這兩個方法的作用是什么旧噪,以及ConcatAdapter究竟是怎么將子Adapter串聯(lián)起來的吨娜。這些問題的答案,我都會在后面的內(nèi)容詳細(xì)介紹舌菜。
3. ConcatAdapter的基本架構(gòu)
??了解了ConcatAdappter
的基本使用之后萌壳,接下來我們將正式分析ConcatAdapter的實現(xiàn)原理,首先我們從它的基本架構(gòu)說起日月。
??從大的方面說起,ConcatAdappter
的內(nèi)部實現(xiàn)主要分為3層缤骨,分別如下:
- ConcatAdapter層:
ConcatAdappter
實現(xiàn)了RecyclerView#Adapter
的很多方法爱咬,它主要是面對于RecyclerView。- ConcatAdapterController層:
ConcatAdapterController
可以認(rèn)為是ConcatAdapter
的代理類绊起,它接管了ConcatAdapter
的很多方法精拟,包括核心的onCreateViewHolder
、onBindViewHolder
和getItemCount
等方法虱歪,所以各種處理邏輯并不是在ConcatAdapter
中蜂绎,而是在ConcatAdapterController
里面進行的。- Helper層:在這里笋鄙,
ConcatAdapterController
里面用到的Helper類都應(yīng)該屬于Helper層师枣,其中極具代表性的是l兩個類:ViewTypeStorage
主要是用于處理ViewType相關(guān)的邏輯;StableIdStorage
主要是用于處理stableId
相關(guān)的邏輯萧落。在這一層的類践美,主要的作用幫助ConcatAdapterController
處理相關(guān)邏輯。
??整個執(zhí)行流程如下圖:
??基本結(jié)構(gòu)大致都了解找岖,接下來我們就一層一層的分析陨倡。
(1). ConcatAdapter
??ConcatAdapter我們應(yīng)該都非常的熟悉,其實它內(nèi)部的實現(xiàn)跟我們平時使用的Adapter沒有多大的差別许布,常用的方法就那么幾個兴革,可能不同的地方就在于ConcatAdapter
將實現(xiàn)邏輯放在了ConcatAdapterController
里面的。盡管如此蜜唾,我們還是要特別說明幾個地方杂曲,如下:
- 在通過ConcatAdapter的構(gòu)造方法構(gòu)造一個對象時庶艾,我們會發(fā)現(xiàn)構(gòu)造方法上有一個很重要的參數(shù)--
Config
,盡管我們可以調(diào)用不帶Config
的構(gòu)造方法解阅,但是實際上也會傳遞一個Config#DEFAULT
對象落竹。這個Config
非常的重要,里面記錄了組合Adapter在處理ViewType和StableId上采用的策略货抄。關(guān)于這兩個細(xì)節(jié)述召,這里先不展開,我們后續(xù)后內(nèi)容專門的分析他倆蟹地。ConcatAdapter
不能通過setHasStableIds
方法設(shè)置Adapter支持stableId积暖。如果想要支持stableId,要分為兩步:首先要在Config
將StableIdMode
設(shè)置為ISOLATED_STABLE_IDS
或者SHARED_STABLE_IDS
;其次添加進來的子Adapter必須支持stableId怪与。ConcatAdapter
不能通過setStateRestorationPolicy
方法設(shè)置RecyclerView恢復(fù)狀態(tài)的策略夺刑。可能有些同學(xué)不知道setStateRestorationPolicy
方法是什么分别,我在這里科普一下遍愿。我們都知道Activity在某些情況下會發(fā)生重建行為,重建之后需要恢復(fù)之前的狀態(tài)耘斩,比如說RecyclerView要恢復(fù)到重建之前的位置沼填,但是在這之前,是無腦的恢復(fù)括授,沒有討論RecyclerView數(shù)據(jù)為空的情況坞笙,所以在RecyclerView數(shù)據(jù)為空的時候,我們直接恢復(fù)之前的滑動位置是不會生效的荚虚,此時如果想要等到RecyclerView不為空時才生效薛夜,就需要setStateRestorationPolicy
方法。從1.2.0版本開始版述,RecyclerView
增加了一個setStateRestorationPolicy
方法來保證盡管數(shù)據(jù)為空狀態(tài)也可以正程堇剑恢復(fù)。其中StateRestorationPolicy
一共有三種模式院水,如下:
名稱 | 含義 |
---|---|
ALLOW | 表示pending的數(shù)據(jù)立即恢復(fù)腊徙,不管數(shù)據(jù)是否為空,這個模式跟以前的 邏輯是一致的 |
PREVENT_WHEN_EMPTY | 表示當(dāng)數(shù)據(jù)為空不恢復(fù)pending的數(shù)據(jù)檬某,當(dāng)數(shù)據(jù)不為空的時候撬腾,才嘗試 著恢復(fù)狀態(tài)。 |
PREVENT | 表示永遠不恢復(fù)恢恼。 |
而在
ConcatAdapter
里面民傻,StateRestorationPolicy就變得較為復(fù)雜一些,因為每個Adapter設(shè)置的模式可能會不一樣,所以就需要統(tǒng)一下漓踢。在ConcatAdapter
里面牵署,采用的方式:首先如果有一個Adapter設(shè)置為PREVENT
,那么所有的Adapter都是PREVENT
模式;然后喧半,如果有一個Adapter設(shè)置為PREVENT_WHEN_EMPTY
奴迅,并且當(dāng)前所有Adapter的itemCount總和為0,那么所有的Adapter都是PREVENT_WHEN_EMPTY
模式挺据;其它的情況表示所有Adapter都是ALLOW
模式取具。這一塊的邏輯在ConcatAdapterController
的computeStateRestorationPolicy
方法里面,這個方法在我們每次調(diào)用addAdapter方法添加一個新的Adapter或者removeAdapter方法移除一個Adapter都會被調(diào)用扁耐,有興趣的同學(xué)可以看一下這個方法的代碼暇检。
(2). ConcatAdapterController
??ConcatAdapterController
相對于ConcatAdapter
來說,要復(fù)雜一些婉称,我們先來梳理大體的實現(xiàn)块仆。
??在ConcatAdapterController
內(nèi)部,每個子Adapter都會被封裝成為一個NestedAdapterWrapper
類王暗,所以ConcatAdapterController
的所有回調(diào)方法都是直接或者間接通過調(diào)用NestedAdapterWrapper
對應(yīng)的方法實現(xiàn)邏輯悔据。而每個方法里面所需要的NestedAdapterWrapper
對象都是通過兩種方式來獲取的:
- 從
ConcatAdapterController
的緩存數(shù)組mWrappers
獲取:當(dāng)我們調(diào)用addAdapter方法俗壹,會將每個Adapter包裝成為一個NestedAdapterWrapper
對象蜜暑,同時會將這個對象添加到mWrappers
數(shù)組里面去,可以通過position其他地方直接獲取策肝。- 從
ViewTypeStorage
里面獲取:ViewTypeStorage
會通過不同的ViewType緩存不同的NestedAdapterWrapper
,可以通過ViewType來獲取隐绵。
??區(qū)分一下ConcatAdapterController
所有需要NestedAdapterWrapper
對象的方法之众,只有onCreateViewHolder
方法是通過方式2獲取的,其他方法都是通過方式1獲取的依许。正因為如此差別棺禾,就會出現(xiàn)一個特別的現(xiàn)象就是,一個子Adapter的onBindViewHolder方法里面帶ViewHolder峭跳,不一定是自己的onCreateViewHolder方法創(chuàng)建膘婶,因為ConcatAdapterController
在onCreateViewHolder
方法里面和onBindViewHolder
方法里面使用的NestedAdapterWrapper
對象不一定是同一個。這一點大家一定要特別注意蛀醉。具體是什么情況下才會出現(xiàn)對象不一樣的問題悬襟,這個在分析ViewType的時候會重點介紹。
??ConcatAdapterController
除了將ConcatAdapter
的回調(diào)分發(fā)到每個子Adapter里面拯刁,還有一個作用就是將每個子Adapter數(shù)據(jù)變換的通知同步到ConcatAdapter
里面去脊岳,因為從類圖上來看,ConcatAdapterController
實現(xiàn)了NestedAdapterWrapper.Callback
接口,每個子Adapter都會通過該接口來通知數(shù)據(jù)變化的信息割捅。
??而Helper層涉及到地方比較零散奶躯,不方便分析,這里就不展開亿驾。接下來嘹黔,我們將分析ConcatAdapter的核心--ViewType和stablId
4. ViewType的處理策略
??將多個Adapter組合到一個Adapter里面,我們需要考慮一個問題莫瞬,就是如果子Adapter有可能返回相同的ViewType儡蔓,面對這種情況,ConcatAdapter
應(yīng)該讓哪個子Adapter來創(chuàng)建ViewHolder呢乏悄?這是一個非常重要的浙值。我們先來看一下Config針對于ViewType已有處理策略,即isolateViewTypes
不同取值的含義檩小。
取值 | 含義 |
---|---|
true | 表示子Adapter相互隔離ViewType开呐,互不影響。比如說有兩個Adapter返回相同的ViewType规求, 那么還是自己處理自己的筐付,在onBindViewHolder方法里面使用的ViewHolder,肯定是自己的 onCreateViewHolder方法創(chuàng)建出來的阻肿。 |
false | 表示所有的子Adapter共享ViewType瓦戚,以及共享ViewHolder。比如說Adapter A和Adapter B 返回了相同的ViewType丛塌,在Adapter A onBindViewHolder 方法里面的ViewHolder有可能是Adapter B的onCreateViewHolder出來的较解。 |
??我們在使用的時候,可以直接通過設(shè)置這個字段的值赴邻,以達到不同的目的印衔。但是有沒有思考過,ConcatAdapter是怎么處理的呢姥敛?接下來我們將正式這兩種策略的實現(xiàn)原理奸焙,不過在這之前我們來了解一下實現(xiàn)ViewType處理策略整體結(jié)構(gòu)。
(1).ViewTypeStorage
??ConcatAdapterContrller在處理ViewType時彤敛,會根據(jù)我們isolateViewTypes
不同取值創(chuàng)建不同ViewTypeStorage
對象与帆,我們先來看一下這個接口的結(jié)構(gòu):
??我分別解釋一下這兩個方法,含義如下表:
方法名 | 含義 |
---|---|
getWrapperForGlobalType | 該方法的作用是通過傳入進入的ViewType獲取一個NestedAdapterWrapper 對象墨榄。ConcatAdapterContrller 的onCreateViewHolder方法就是通過該方法獲取獲取的 NestedAdapterWrapper
|
createViewTypeWrapper | 該方法的作用是通過傳入的進來NestedAdapterWrapper 的對象玄糟,創(chuàng)建一個 ViewTypeLookup 對象。這其中渠概,ViewTypeLookup 會將傳入進來的NestedAdapterWrapper 對象緩存起來茶凳,方便 getWrapperForGlobalType 方法通過ViewType獲取嫂拴。 |
??大家可能會對ViewTypeLookup
有疑惑,在這里贮喧,我簡單的解釋一下筒狠,先來看一下ViewTypeLookup
的類圖:
??
ViewTypeLookup
的作用就是將localType和globalType相互轉(zhuǎn)換。那么怎么理解這兩個type呢箱沦?我們可以這樣認(rèn)為:localType
是每個子Adapter返回產(chǎn)生的辩恼,globalType
是ConcatAdapter產(chǎn)生的。當(dāng)ConcatAdapter需要將ViewType傳遞給子Adapter谓形,就先要將它的globalType
轉(zhuǎn)換成為子Adapter能識別的localType
;同時灶伊,ConcatAdapter產(chǎn)生的ViewType并不是它自己產(chǎn)生的,而是調(diào)用每個子Adapter的getItemViewType方法獲取寒跳,然后然后通過localToGlobal方法轉(zhuǎn)換成為globalType
聘萨。這一點,我們可以從ConcatAdapterController
里面找到答案:
public int getItemViewType(int globalPosition) {
// 1. 通過position獲取一個WrapperAndLocalPosition對象童太,這里面封裝的是
// NestedAdapterWrapper和localPosition米辐。
WrapperAndLocalPosition wrapperAndPos = findWrapperAndLocalPosition(globalPosition);
// 2. 調(diào)用NestedAdapterWrapper的getItemViewType方法返回一個ItemViewType。
// 這里返回的ViewType就是globalType书释,NestedAdapterWrapper的內(nèi)部進行了一次localToGlobal轉(zhuǎn)換翘贮。
int itemViewType = wrapperAndPos.mWrapper.getItemViewType(wrapperAndPos.mLocalPosition);
releaseWrapperAndLocalPosition(wrapperAndPos);
return itemViewType;
}
??如上方法分為兩步,我簡單的總結(jié)一下:
- 先是通過
globalPosition
獲取一個WrapperAndLocalPosition
爆惧,這里面封裝的是 NestedAdapterWrapper和localPosition狸页。很明顯,這里用到的NestedAdapterWrapper
是通過上面介紹的方式1獲取的扯再。而findWrapperAndLocalPosition
得非常的重要芍耘,在ConcatAdapterController
內(nèi)部的很多地方都在調(diào)用,它的作用就是通過globalPosition
找到對應(yīng)的NestedAdapterWrapper
,這里就不展開細(xì)講了熄阻,有興趣的同學(xué)可以了解一下齿穗。- 調(diào)用
NestedAdapterWrapper
的getItemViewType
。在NestedAdapterWrapper
的getItemViewType
的內(nèi)部饺律,其實分為兩步:首先是調(diào)用子Adapter的getItemViewType
方法獲取localType
;然后就是調(diào)用ViewTypeLookup
的localToGlobal
方法將localType
轉(zhuǎn)換成為globalType
跺株。
??從整體來說复濒,ViewTypeStorage
是服務(wù)于ConcatAdapter,因此不管子Adapter有多少個乒省,只會有一個ViewTypeStorage
對象巧颈;而ViewTypeLookup
是服務(wù)于子Adapter,因此有多少個子Adapter袖扛,就會創(chuàng)建多少個ViewTypeLookup
對象砸泛。而ViewTypeLookup
的創(chuàng)建是在NestedAdapterWrapper
的構(gòu)造方法里面進行的:
NestedAdapterWrapper(
Adapter<ViewHolder> adapter,
final Callback callback,
ViewTypeStorage viewTypeStorage,
StableIdStorage.StableIdLookup stableIdLookup) {
// ······
mViewTypeLookup = viewTypeStorage.createViewTypeWrapper(this);
// ······
}
(2). 隔離ViewType
??接下來十籍,我將重點分析ViewType的兩種策略。首先唇礁,我們來看隔離策略勾栗。從ConcatAdapterController
的構(gòu)造方法里面,我們可以知道盏筐,隔離策略用到的ViewTypeStorage
的實現(xiàn)類是IsolatedViewTypeStorage
围俘。我們來看一下IsolatedViewTypeStorage
的實現(xiàn):
class IsolatedViewTypeStorage implements ViewTypeStorage {
SparseArray<NestedAdapterWrapper> mGlobalTypeToWrapper = new SparseArray<>();
int mNextViewType = 0;
int obtainViewType(NestedAdapterWrapper wrapper) {
int nextId = mNextViewType++;
mGlobalTypeToWrapper.put(nextId, wrapper);
return nextId;
}
@NonNull
@Override
public NestedAdapterWrapper getWrapperForGlobalType(int globalViewType) {
NestedAdapterWrapper wrapper = mGlobalTypeToWrapper.get(
globalViewType);
if (wrapper == null) {
throw new IllegalArgumentException("Cannot find the wrapper for global"
+ " view type " + globalViewType);
}
return wrapper;
}
@Override
@NonNull
public ViewTypeLookup createViewTypeWrapper(
@NonNull NestedAdapterWrapper wrapper) {
return new WrapperViewTypeLookup(wrapper);
}
void removeWrapper(@NonNull NestedAdapterWrapper wrapper) {
for (int i = mGlobalTypeToWrapper.size() - 1; i >= 0; i--) {
NestedAdapterWrapper existingWrapper = mGlobalTypeToWrapper.valueAt(i);
if (existingWrapper == wrapper) {
mGlobalTypeToWrapper.removeAt(i);
}
}
}
class WrapperViewTypeLookup implements ViewTypeLookup {
private SparseIntArray mLocalToGlobalMapping = new SparseIntArray(1);
private SparseIntArray mGlobalToLocalMapping = new SparseIntArray(1);
final NestedAdapterWrapper mWrapper;
WrapperViewTypeLookup(NestedAdapterWrapper wrapper) {
mWrapper = wrapper;
}
@Override
public int localToGlobal(int localType) {
int index = mLocalToGlobalMapping.indexOfKey(localType);
if (index > -1) {
return mLocalToGlobalMapping.valueAt(index);
}
// get a new key.
int globalType = obtainViewType(mWrapper);
mLocalToGlobalMapping.put(localType, globalType);
mGlobalToLocalMapping.put(globalType, localType);
return globalType;
}
@Override
public int globalToLocal(int globalType) {
int index = mGlobalToLocalMapping.indexOfKey(globalType);
if (index < 0) {
throw new IllegalStateException("requested global type " + globalType + " does"
+ " not belong to the adapter:" + mWrapper.adapter);
}
return mGlobalToLocalMapping.valueAt(index);
}
@Override
public void dispose() {
removeWrapper(mWrapper);
}
}
}
??針對于IsolatedViewTypeStorage
, 我們重點分析getWrapperForGlobalType
方法和createViewTypeWrapper
方法。
- getWrapperForGlobalType方法:我們可以從上面的實現(xiàn)可以看出來琢融,
NestedAdapterWrapper
對象是從一個數(shù)組里面獲取界牡,其中key是globalViewType
。那么NestedAdapterWrapper
對象是怎么放進去的呢漾抬?我們簡單尋找一下調(diào)用關(guān)系就知道:是在IsolatedViewTypeStorage
的obtainViewType
方法放進去的宿亡,整個調(diào)用關(guān)系如下圖:
總而言之,就是在getItemViewType
放入進去的纳令。這里挽荠,我們需要特別的注意,如果ViewType采用隔離策略泊碑,那么子Adapter千萬不能返回相同的ViewType坤按。因為我們從實現(xiàn)來看,NestedAdapterWrapper
是依靠ViewType作為存儲的馒过,那么如果有兩個Adapter返回相同的ViewType臭脓,會導(dǎo)致獲取NestedAdapterWrapper
不是正確的,也就是前面說的腹忽,onCreateViewHolder調(diào)用的Adapter和onBindViewHolder的Adapter可能不是同一個對象来累。這個問題在隔離策略應(yīng)該嚴(yán)格避免,否則容易出現(xiàn)莫名其妙的錯誤窘奏。- createViewTypeWrapper方法:此方法的作用是用來創(chuàng)建
ViewTypeLookup
嘹锁,從上面的代碼中我們可以得知,與IsolatedViewTypeStorage
對應(yīng)的ViewTypeLookup
實現(xiàn)類是WrapperViewTypeLookup
着裹。從前面的介紹领猾,我們可以知道,createViewTypeWrapper
方法是在NestedAdapterWrapper
的構(gòu)造方法里面被調(diào)用的骇扇,在創(chuàng)建的同時還把NestedAdapterWrapper
對象傳進來的摔竿,這里就為了后來localToGlobal
方法里面存儲NestedAdapterWrapper
對象埋下了伏筆。前文已經(jīng)介紹過少孝,ViewTypeLookup
是面向子Adapter的继低,所以ViewTypeLookup
記錄的NestedAdapterWrapper
對象就是跟它對應(yīng)的NestedAdapterWrapper
對象。
(3). 共享ViewType
??說完了隔離策略的實現(xiàn)稍走,我們再來看看共享策略袁翁。從結(jié)構(gòu)來說柴底,共享策略使用的是SharedIdRangeViewTypeStorage
,同時與它對應(yīng)的ViewTypeLookup
實現(xiàn)類是WrapperViewTypeLookup
;從實現(xiàn)上來說粱胜,共享策略在getWrapperForGlobalType
方法也是通過ViewType獲取NestedAdapterWrapper
對象柄驻,也是在localToGlobal
方面里面將記錄的NestedAdapterWrapper
對象存儲在一個數(shù)組里面,這些跟隔離策略都是一致的年柠。唯一不一致的是凿歼,NestedAdapterWrapper
數(shù)組采用的是SparseArray<List<NestedAdapterWrapper>>
數(shù)據(jù)數(shù)據(jù)結(jié)構(gòu),也就是說冗恨,同一個ViewType可能有多個NestedAdapterWrapper
對應(yīng)答憔,這也是共享策略的特色,子Adapter可以返回相同的ViewType掀抹。那么相同的ViewType虐拓,SharedIdRangeViewTypeStorage
是怎么確定該返回哪一個NestedAdapterWrapper
的呢?我們來簡單的看一下getWrapperForGlobalType
方法的實現(xiàn):
public NestedAdapterWrapper getWrapperForGlobalType(int globalViewType) {
List<NestedAdapterWrapper> nestedAdapterWrappers = mGlobalTypeToWrapper.get(
globalViewType);
if (nestedAdapterWrappers == null || nestedAdapterWrappers.isEmpty()) {
throw new IllegalArgumentException("Cannot find the wrapper for global view"
+ " type " + globalViewType);
}
// just return the first one since they are shared
return nestedAdapterWrappers.get(0);
}
??看上面的實現(xiàn)傲武,我們可以知道蓉驹,getWrapperForGlobalType
方法直接返回的是數(shù)組第一個元素。所以揪利,共享策略不能保證态兴,onBindViewHolder使用的ViewHolder是自己Adapter的onCreateViewHolder方法創(chuàng)建來的,這一點大家一定要注意疟位。在這里瞻润,我有一個疑問,既然始終返回的是數(shù)組第一個元素甜刻,有必要用一個數(shù)組來存儲嗎绍撞?我不清楚Google爸爸是怎么考慮的。
5. StableId的處理策略
??Config里面還有一個配置就是StableId的模式得院,從官方的文檔來看傻铣,我們可以知道stableId一共有三個模式,分別如下:
模式 | 含義 |
---|---|
NO_STABLE_IDS | 這個模式比較簡單祥绞,就是指Adapter不支持stableId非洲。 |
ISOLATED_STABLE_IDS | 表示子Adapter之間采用隔離策略,在這個模式下蜕径,子Adapter不同考慮其他 Adapter的存在怪蔑,因為在這個模式里面,ConcatAdapter 會覆蓋子Adapter自 己生成的stableId丧荐,由它統(tǒng)一給每個item分配stableId,這樣我們定義子Adapter 的時候喧枷,就不用其他的Adapter虹统。注意的是弓坞,此時子Adapter的getItemId 方法和ViewHolder的getItemId方法的返回值是不一樣的,我們?nèi)绻枰猻tableId 的話车荔,ViewHolder的getItemId方法是最可靠的渡冻。 |
SHARED_STABLE_IDS | 表示子Adapter之間采用共享策略,在這個模式忧便,由子Adapter自己生成stableId族吻, ConcatAdapter不會覆蓋子Adapter的stableId。因為stableId的唯一性原則珠增,所 以每個子Adapter在生成stableId時需要考慮其他子Adapter的存在超歌,必須保證生 成的stableId的唯一性。 |
??stableId的設(shè)計跟ViewType的設(shè)計非常的類似蒂教,都是一個Storage
類和多個Lookup
類巍举。在stableId 結(jié)構(gòu)中,StableIdStorage
是服務(wù)于ConcatAdapter凝垛,因為只會創(chuàng)建一個對象懊悯;StableIdLookup
服務(wù)于子Adapter,因此每個子Adapter都會創(chuàng)建StableIdLookup
對象梦皮。
??我們來簡單的看一下這兩個接口的定義,uml類圖如下:
??兩個接口的結(jié)構(gòu)從類圖可以看出炭分,我針對于他們的方法特別解釋一下:
- createStableIdLookup方法:顧名思義,就是創(chuàng)建一個
StableIdLookup
對象剑肯。在ConcatAdapterController的構(gòu)造方法中捧毛,首先會根據(jù)Config里面配置創(chuàng)建不同的StableIdStorage實現(xiàn)類對象;其次在創(chuàng)建NestedAdapterWrapper
的時候退子,會直接調(diào)用createStableIdLookup
方法創(chuàng)建一個StableIdLookup
對象岖妄,與新添加進來的子Adapter綁定,子Adapter需要的StableIdLookup
對象就是在創(chuàng)建的寂祥。- localToGlobal方法:將子Adapter轉(zhuǎn)換成為ConcatAdapter需要的globalId荐虐。因為這個方法實現(xiàn)不同,所以就區(qū)分出來了三種策略模式丸凭。
??我們大致了解了每個模式的含義福扬,我們分別來看一下每個模式的實現(xiàn)。
(1). 隔離策略
??在隔離策略中惜犀,StableIdStorage
的實現(xiàn)類是IsolatedStableIdStorage
铛碑,StableIdLookup
的實現(xiàn)類是IsolatedStableIdStorage
。
??在隔離策略中虽界,IsolatedStableIdStorage
會把將每個子Adapter抹平汽烦,因此每個子Adapter生成的stableId都會經(jīng)過localToGlobal
方法轉(zhuǎn)換一次,因此我們直接來看localToGlobal
方法:
@Override
public long localToGlobal(long localId) {
Long globalId = mLocalToGlobalLookup.get(localId);
if (globalId == null) {
globalId = obtainId();
mLocalToGlobalLookup.put(localId, globalId);
}
return globalId;
}
??這個方法主要經(jīng)過兩步:
- 判斷緩存中是否已經(jīng)有stableId莉御,如果有撇吞,直接返回俗冻;如果沒有則進行第二步。
- 調(diào)用
obtainId
獲取一個新的stableId牍颈。
??從這里就可以應(yīng)證前面所說的迄薄,隔離策略會覆蓋子Adapter生成的stableId。在隔離策略中煮岁,不同的Adapter返回相同的stableId也是沒有關(guān)系的讥蔽,因為不同的Adapter擁有不同的StableIdLookup
對象,進而mLocalToGlobalLookup
緩存也是不一樣的画机,所以他們互不影響冶伞。
(2). 共享策略
??在隔離策略中,StableIdStorage
的實現(xiàn)類是SharedPoolStableIdStorage
色罚,StableIdLookup
的實現(xiàn)類是SameIdLookup
碰缔。我們直接來看一下localToGlobal
方法的實現(xiàn):
@Override
public long localToGlobal(long localId) {
return localId;
}
??共享策略的實現(xiàn)很簡單,就是將localId作為globalId戳护。從這里金抡,我們就可以知道為啥使用共享策略時,必須保證子Adapter不能生成不同的stableId腌且。
6. 總結(jié)
??到這里梗肝,本文對ConcatAdapter
的介紹結(jié)束了,我在這里做一個簡單的總結(jié)铺董。
- ConcatAdapter的架構(gòu)主要分為三層巫击,分別是ConcatAdapter、ConcatAdapterController和Helper精续。Helper層主要是包括:ViewTypeStorage--用來處理ViewType的坝锰;StableIdStorage--用來處理stableId;NestedAdapterWrapper--里面封裝了子Adapter重付、ViewTypeStorage和StableIdStorage等相關(guān)類顷级。
- ViewType和stableId的處理都采用經(jīng)典的策略者模式,主要思想是通過接口將ViewType和stabId的處理方式抽象出來确垫,然后不同策略下弓颈,使用不同的實現(xiàn)類,這樣的實現(xiàn)能保證邏輯清晰删掀,可擴展性高翔冀。同時,其中涉及到的
ViewTypeStorage
和StableIdStorage
是服務(wù)于ConcatAdapter
披泪,ViewTypeLookup
和StableIdLookup
服務(wù)于子Adapter纤子,這一點大家一定要謹(jǐn)記。