RecyclerView擴展(七) - ConcatAdapter源碼分析

??最近在學(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. Android-MergeAdapter閑聊片
  2. 使用 ConcatAdapter 順序連接其他 Adapter

1.概述

??ConcatAdapter是RecyclerView是在1.2.0版本上推出的新組件背犯,在alpha04版本之前它叫MergeAdapter玻墅,但是從alpha04版本開始装诡,Google爸爸就將其改名為ConcatAdapter吞歼。所以圈膏,可能有些同學(xué)對此有些陌生,但是ConcatAdapter實際上就是之前的MergeAdapter篙骡,用法和實現(xiàn)都是差不多的稽坤。
??相信大家在開發(fā)過程中都會遇到一種情況:RecyclerView在使用Adapter加載數(shù)據(jù)的時候,可能會區(qū)分多種ViewType糯俗,一般的處理方式都是通過重新AdaptergetItemViewType方法返回不同的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

  1. ConcatAdapter的基本使用。
  2. ConcatAdapter的基本架構(gòu)厌殉。
  3. ConcatAdapter針對ViewType的處理食绿。
  4. ConcatAdaprer針對stableId的處理。

2. ConcatAdapter的基本使用

??在正式分析ConcatAdapter源碼之前公罕,我們先來了解一下ConcatAdapter的基本使用器紧。我們就按照上面的Header、Content和Footer三個部分的場景楼眷,實現(xiàn)一個小小的Demo铲汪,具體的效果如下圖:



??首先,我們要先定義三個Adapter罐柳,用來加載不同部分的布局掌腰,假設(shè)分別稱為:HeaderAdapter、ContentAdapter 和FooterAdapter张吉。這里就不詳細(xì)展開具體的實現(xiàn)齿梁,都是比較簡單的邏輯。
?定義好三個Adapter之后,接下來就是使用ConcatAdapter將三個Adapter組合起來勺择。組合步驟主要分為如下2步:

  1. 定義ConcatAdapter的Config创南。主要是配置ViewType是否相互隔離,以及stableId的策略省核。
  2. 使用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的時候,setIsolateViewTypessetStableIdMode這兩個方法的作用是什么旧噪,以及ConcatAdapter究竟是怎么將子Adapter串聯(lián)起來的吨娜。這些問題的答案,我都會在后面的內(nèi)容詳細(xì)介紹舌菜。

3. ConcatAdapter的基本架構(gòu)

??了解了ConcatAdappter的基本使用之后萌壳,接下來我們將正式分析ConcatAdapter的實現(xiàn)原理,首先我們從它的基本架構(gòu)說起日月。
??從大的方面說起,ConcatAdappter的內(nèi)部實現(xiàn)主要分為3層缤骨,分別如下:

  1. ConcatAdapter層:ConcatAdappter實現(xiàn)了RecyclerView#Adapter的很多方法爱咬,它主要是面對于RecyclerView。
  2. ConcatAdapterController層:ConcatAdapterController可以認(rèn)為是ConcatAdapter的代理類绊起,它接管了ConcatAdapter的很多方法精拟,包括核心的onCreateViewHolderonBindViewHoldergetItemCount等方法虱歪,所以各種處理邏輯并不是在ConcatAdapter中蜂绎,而是在ConcatAdapterController里面進行的。
  3. 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里面的。盡管如此蜜唾,我們還是要特別說明幾個地方杂曲,如下:

  1. 在通過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)容專門的分析他倆蟹地。
  2. ConcatAdapter不能通過setHasStableIds方法設(shè)置Adapter支持stableId积暖。如果想要支持stableId,要分為兩步:首先要在ConfigStableIdMode設(shè)置為ISOLATED_STABLE_IDS或者SHARED_STABLE_IDS;其次添加進來的子Adapter必須支持stableId怪与。
  3. 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模式取具。這一塊的邏輯在ConcatAdapterControllercomputeStateRestorationPolicy方法里面,這個方法在我們每次調(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對象都是通過兩種方式來獲取的:

  1. ConcatAdapterController的緩存數(shù)組mWrappers獲取:當(dāng)我們調(diào)用addAdapter方法俗壹,會將每個Adapter包裝成為一個NestedAdapterWrapper對象蜜暑,同時會將這個對象添加到mWrappers數(shù)組里面去,可以通過position其他地方直接獲取策肝。
  2. ViewTypeStorage里面獲取:ViewTypeStorage會通過不同的ViewType緩存不同的NestedAdapterWrapper,可以通過ViewType來獲取隐绵。

??區(qū)分一下ConcatAdapterController所有需要NestedAdapterWrapper對象的方法之众,只有onCreateViewHolder方法是通過方式2獲取的,其他方法都是通過方式1獲取的依许。正因為如此差別棺禾,就會出現(xiàn)一個特別的現(xiàn)象就是,一個子Adapter的onBindViewHolder方法里面帶ViewHolder峭跳,不一定是自己的onCreateViewHolder方法創(chuàng)建膘婶,因為ConcatAdapterControlleronCreateViewHolder方法里面和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 AonBindViewHolder方法里面的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é)一下:

  1. 先是通過globalPosition獲取一個WrapperAndLocalPosition爆惧,這里面封裝的是 NestedAdapterWrapper和localPosition狸页。很明顯,這里用到的NestedAdapterWrapper是通過上面介紹的方式1獲取的扯再。而findWrapperAndLocalPosition得非常的重要芍耘,在ConcatAdapterController內(nèi)部的很多地方都在調(diào)用,它的作用就是通過globalPosition找到對應(yīng)的NestedAdapterWrapper,這里就不展開細(xì)講了熄阻,有興趣的同學(xué)可以了解一下齿穗。
  2. 調(diào)用NestedAdapterWrappergetItemViewType。在NestedAdapterWrappergetItemViewType的內(nèi)部饺律,其實分為兩步:首先是調(diào)用子Adapter的getItemViewType方法獲取localType;然后就是調(diào)用ViewTypeLookuplocalToGlobal方法將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方法。

  1. getWrapperForGlobalType方法:我們可以從上面的實現(xiàn)可以看出來琢融,NestedAdapterWrapper對象是從一個數(shù)組里面獲取界牡,其中key是globalViewType。那么NestedAdapterWrapper對象是怎么放進去的呢漾抬?我們簡單尋找一下調(diào)用關(guān)系就知道:是在IsolatedViewTypeStorageobtainViewType方法放進去的宿亡,整個調(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)莫名其妙的錯誤窘奏。
  2. 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)從類圖可以看出炭分,我針對于他們的方法特別解釋一下:

  1. 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)建的寂祥。
  2. 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)過兩步:

  1. 判斷緩存中是否已經(jīng)有stableId莉御,如果有撇吞,直接返回俗冻;如果沒有則進行第二步。
  2. 調(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é)铺董。

  1. ConcatAdapter的架構(gòu)主要分為三層巫击,分別是ConcatAdapter、ConcatAdapterController和Helper精续。Helper層主要是包括:ViewTypeStorage--用來處理ViewType的坝锰;StableIdStorage--用來處理stableId;NestedAdapterWrapper--里面封裝了子Adapter重付、ViewTypeStorage和StableIdStorage等相關(guān)類顷级。
  2. ViewType和stableId的處理都采用經(jīng)典的策略者模式,主要思想是通過接口將ViewType和stabId的處理方式抽象出來确垫,然后不同策略下弓颈,使用不同的實現(xiàn)類,這樣的實現(xiàn)能保證邏輯清晰删掀,可擴展性高翔冀。同時,其中涉及到的ViewTypeStorageStableIdStorage是服務(wù)于ConcatAdapter披泪,ViewTypeLookupStableIdLookup服務(wù)于子Adapter纤子,這一點大家一定要謹(jǐn)記。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市控硼,隨后出現(xiàn)的幾起案子跌捆,更是在濱河造成了極大的恐慌,老刑警劉巖象颖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異姆钉,居然都是意外死亡说订,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門潮瓶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陶冷,“玉大人,你說我怎么就攤上這事毯辅」÷祝” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵思恐,是天一觀的道長沾谜。 經(jīng)常有香客問我,道長胀莹,這世上最難降的妖魔是什么基跑? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮描焰,結(jié)果婚禮上媳否,老公的妹妹穿的比我還像新娘。我一直安慰自己荆秦,他們只是感情好篱竭,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著步绸,像睡著了一般掺逼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上靡努,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天坪圾,我揣著相機與錄音,去河邊找鬼惑朦。 笑死兽泄,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的漾月。 我是一名探鬼主播病梢,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蜓陌?” 一聲冷哼從身側(cè)響起觅彰,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钮热,沒想到半個月后填抬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡隧期,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年飒责,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仆潮。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡宏蛉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出性置,到底是詐尸還是另有隱情拾并,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布鹏浅,位于F島的核電站嗅义,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏篡石。R本人自食惡果不足惜芥喇,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凰萨。 院中可真熱鬧继控,春花似錦、人聲如沸胖眷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽懂盐。三九已至,卻和暖如春登馒,著一層夾襖步出監(jiān)牢的瞬間境析,已是汗流浹背囚枪。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留劳淆,地道東北人链沼。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像沛鸵,于是被迫代替她去往敵國和親括勺。 傳聞我的和親對象是個殘疾皇子缆八,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345