相關(guān)源碼和示例
RecyclerView面對多類型Adapter時候面臨的問題
RecyclerView出來這么久了相信應(yīng)該大家都在用了。使用基本三步憔鬼。
1龟劲、創(chuàng)建一個ViewHolder類胃夏。
2、創(chuàng)建一個Adapter昌跌。
3仰禀、向Adapter添加或者刪除數(shù)據(jù)。
當(dāng)然如果大家都是在用單ViewHolder的Adapter上面的代碼寫起來還是很簡單的蚕愤,沒什么問題答恶。但是如果遇到多類型ViewType怎么辦?
常規(guī)寫法大家會在Adapter中復(fù)寫
public int getItemViewType(int position) {
// 通過postion拿到對應(yīng)的數(shù)據(jù)审胸,然后處理返回不同的Type
}
然后在onCreateViewHolder方法中根據(jù)不同的ViewType返回不同的ViewHolder亥宿。在onBindViewHolder進行數(shù)據(jù)綁定的時候,再判斷不同的ViewHolder進行不同的綁定處理砂沛。
寫的多了烫扼,會發(fā)現(xiàn)這里有個問題,Adapter本身要求一個ViewHolder的范型碍庵,單ViewHolder這里直接寫對應(yīng)的ViewHolder就行了捌袜,那多ViewType怎么辦薄辅?寫B(tài)aseViewHolder嗎此蜈?onBindViewHolder時候還是需要繼續(xù)判斷ViewHolder類型适荣。其實這時候Adapter本身要傳的ViewHolder范型其實已經(jīng)沒什么意義了。
這還不是最主要的苹享,如果項目復(fù)雜了双絮,大家用RecyclerView多了,會發(fā)現(xiàn)得问,不同界面經(jīng)常會遇到復(fù)用ViewHolder的情況囤攀。經(jīng)常會遇到界面0是ViewHolder0,ViewHolder2宫纬,ViewHolder4的組合焚挠。界面1是ViewHolder2,ViewHolder3的組合漓骚,界面2是ViewHolder0蝌衔,ViewHolder4的組合。這時候我們是每個界面都寫一個Adapter嗎蝌蹂?通常大家都會這么處理噩斟,寫的多了會發(fā)現(xiàn),Adapter在寫大量重復(fù)代碼叉信。那怎么辦亩冬?
主要是基于這種需求。所以需要用一種優(yōu)雅的方式解決Adapter需要處理多個ViewHolder硼身,而且還需要ViewHolder在不同界面能非常簡單方便的被復(fù)用硅急。
思路
我們要做什么?我們要做的就是把盡量多的重復(fù)邏輯封裝起來佳遂,避免重復(fù)勞動营袜。另外就是盡可能讓復(fù)用簡單,屬于誰的代碼就讓誰處理丑罪。
既然Adapter存在綁定多個不同ViewHodler的情況荚板,那我們最好有個通用的Adapter,這個Adapter能處理任意多個不同ViewHolder的create和bind吩屹,然后對應(yīng)的界面自己去注冊自己需要的ViewHolder跪另,你需要什么ViewHolder你就注冊什么ViewHolder。
我們現(xiàn)在重點關(guān)注Adapter的onCreateViewHolder和onBindViewHolder方法煤搜。
onCreateViewHolder是用來創(chuàng)建ViewHolder的免绿,參數(shù)只有ViewGroup和ViewType,ViewGroup是讓我們用來填充ViewHolder進去的擦盾。實際能用來區(qū)分不同ViewHolder的就只有ViewType了嘲驾。
那ViewType是從哪里來的?是從getItemViewType來的迹卢。getItemViewType可以通過position拿到我們的數(shù)據(jù)源辽故,然后通過判斷數(shù)據(jù)源的類類型來判斷它是什么ViewType。
好腐碱,現(xiàn)在我們需要一個Adapter必定有的保存數(shù)據(jù)的數(shù)據(jù)源誊垢。這里我們選用List(注意必須是個有序List,而且這個List會被經(jīng)常讀取數(shù)據(jù)症见,ArrayList讀取效率非常高喂走,所以這里用ArrayList是最好的選擇。)筒饰。因為我們需要綁定多個ViewHolder缴啡,所以這里L(fēng)ist的范型,需要是Object瓷们。就是支持任意類型业栅。
回到getItemViewType,從getItemViewType的postion谬晕,我們拿到了List中保存的Data碘裕,那我們怎么知道這個Data要被綁定到什么ViewHolder上呢?所以需要有個register方法來注冊ViewHolder攒钳。那這個方法參數(shù)是什么帮孔?是ViewHolder和ViewHolder對應(yīng)的Data。又因為我們ViewHolder是被onCreateViewHolder時候創(chuàng)建的,所以這個肯定是不能傳實例的文兢。Data也不能是實例晤斩,Data應(yīng)該是被調(diào)用處(通常是Http/Https Api返回)創(chuàng)建實例。所以這里兩個參數(shù)應(yīng)該都是類類型姆坚。
public void register(Class<?> dataType, Class<? extends ViewHolder> viewType)
好了澳泵,我們把注冊的類類型保存在一個Map中。用來記錄每個數(shù)據(jù)類型對應(yīng)的ViewHolder兼呵。又因為getItemViewType返回的是個int來區(qū)分ViewType兔辅,所以我們需要把Data的類類型映射為int,怎么映射击喂?非常簡單用hashCode维苔。所以這個紀(jì)錄data與View綁定關(guān)系的Map的key是個Int保存Data類類型的hashCode,value是ViewHolder的類類型懂昂。
再回到onCreateViewHolder方法介时,通過傳過來的int viewType,我們?nèi)ノ覀儽4嬗成潢P(guān)系的mBindMap中拿到這個ViewType對應(yīng)的ViewHolder的類類型忍法。然后我們就可以通過反射來創(chuàng)建ViewHolder對象了潮尝。
這里注意還有個問題。通常我們寫ViewHolder可能都會寫成下面這樣:
public static class SimpleViewHolder extends RecyclerView.ViewHolder{
public SimpleViewHolder(View itemView) {
super(itemView);
}
}
然后在onCreateViewHolder中通過類似下面的代碼創(chuàng)建SimpleViewHolder饿序。
View view = LayoutInflater.from(context).inflate(R.layout.view_holder_simple,parent,false);
ViewHolder viewHodler = new SimpleViewHolder(view);
其實個人認(rèn)為這是非常不好的寫法勉失。為什么要讓調(diào)用者來創(chuàng)建屬于SimpleViewHolder的itemView?誰的事情交給誰做原探。SimpleViewHolder應(yīng)該對應(yīng)什么View乱凿,SimpleViewHolder自己最清楚。所以我們可以對SimpleViewHolder進行改造咽弦。
public static class SimpleViewHolder extends RecyclerView.ViewHolder{
public SimpleViewHolder(ViewGroup parent) {
super(LayoutInflater.from(parentView.getContext()).inflate(R.layout.simple_view_holder, parentView, false));
}
}
這樣onCreateViewHolder就完全不關(guān)心SimpleViewHolder應(yīng)該對應(yīng)什么View徒蟆,這個應(yīng)該讓SimpelViewHolder自己決定。
回到前面onCreateViewHolder創(chuàng)建ViewHolder的地方型型。這時候我們要約定好段审,被注冊的ViewHolder必須有個參數(shù)為ViewGroup的構(gòu)造方法。
然后繼續(xù)下一步onBindViewHolder闹蒜,onBindViewHolder兩個參數(shù)寺枉,ViewHolder實例和positon(Data實例),我們要做的就是把Data設(shè)置到ViewHolder上绷落,一個ViewHolder該如何綁定數(shù)據(jù)姥闪,肯定是ViewHolder自身最清楚,所以這里ViewHolder必須有個綁定數(shù)據(jù)的方法砌烁。這里我們需要抽象一個ViewHolder的基類筐喳,所有ViewHolder都必須繼承這個ViewHolder,然后這個ViewHodler基類,至少有個綁定數(shù)據(jù)的方法避归,我們暫時取名叫bindData荣月。
好到這里我們基本就完成了,不同ViewType綁定的處理槐脏。
梳理下思路:
第一步:register注冊需要的ViewHolder和ViewHolder對應(yīng)的數(shù)據(jù)Data喉童。
第二步:getViewType返回Data對應(yīng)的viewType
第三步:onCreateViewHolder通過ViewType拿到對應(yīng)的ViewHolder類類型撇寞,通過反射創(chuàng)建ViewHolder實例顿天。
第四步:拿到postion對應(yīng)的數(shù)據(jù),設(shè)置給ViewHolder蔑担。
思路有了剩下的就是填充代碼了牌废。
Head Content和Foot的處理
在實際寫的時候考慮到我們經(jīng)常會遇到需要區(qū)分Head Content和Foot的情況。我們前面那些關(guān)于不同ViewType的處理其實跟Head Content Foot是不沖突的啤握。為了方便擴展實現(xiàn)鸟缕。所以實際寫的時候抽象了一個AbsMultiTypeAdapter出來,AbsMultiTypeAdapter處理了不同Type的創(chuàng)建和綁定排抬。但是AbsMultiTypeAdapter不關(guān)心數(shù)據(jù)源懂从,所有需要調(diào)用數(shù)據(jù)源的方法(包含data的方法,比如addData蹲蒲,removeData)全部改為抽象方法番甩,讓子類去實現(xiàn)。這樣子類可以根據(jù)需要來實現(xiàn)帶有Head Content和Foot的數(shù)據(jù)源届搁。
在MultiTypeAdapterImpl中實際實現(xiàn)了完整的MultiTypeAdapter的功能缘薛。MultiTypeAdapterImpl實現(xiàn)了任意多個類型的Adapter。具體實現(xiàn)方式就是把之前我們習(xí)慣的數(shù)據(jù)源從List變成List套List的List<List<Object>>卡睦,外層List保存不同的Type宴胧,內(nèi)層List是實際對應(yīng)Type包含的數(shù)據(jù)。然后就是處理Postion與Type Offset的相互轉(zhuǎn)換了表锻。詳細(xì)的不再細(xì)說恕齐。
最終效果
上面說了那么多,那最終實際效果是什么瞬逊?我們實現(xiàn)了一個MultiTypeAdapterImpl显歧。它支持任意的ViewHolder以及它們的組合。
使用時候第一步必須通過register方法注冊你需要的ViewHolder和這個ViewHolder對應(yīng)的數(shù)據(jù)類型码耐。當(dāng)然ViewHolder必須繼承AbsViewHolder追迟。然后就可以通過addItem方法給Adapter添加數(shù)據(jù)了,剩下的itemView createViewHolder bindViewHolder骚腥,MultiTypeAdapterImpl會幫你處理敦间。
相當(dāng)于你的Activity里寫的代碼非常少。只有register和addItem。ViewHolder復(fù)用非常輕松簡單廓块。