相關源碼和示例
RecyclerView面對多類型Adapter時候面臨的問題
RecyclerView出來這么久了相信應該大家都在用了。使用基本三步。
1、創建一個ViewHolder類。
2、創建一個Adapter。
3、向Adapter添加或者刪除數據。
當然如果大家都是在用單ViewHolder的Adapter上面的代碼寫起來還是很簡單的,沒什么問題。但是如果遇到多類型ViewType怎么辦?
常規寫法大家會在Adapter中復寫
public int getItemViewType(int position) {
// 通過postion拿到對應的數據,然后處理返回不同的Type
}
然后在onCreateViewHolder方法中根據不同的ViewType返回不同的ViewHolder。在onBindViewHolder進行數據綁定的時候,再判斷不同的ViewHolder進行不同的綁定處理。
寫的多了,會發現這里有個問題,Adapter本身要求一個ViewHolder的范型,單ViewHolder這里直接寫對應的ViewHolder就行了,那多ViewType怎么辦?寫BaseViewHolder嗎?onBindViewHolder時候還是需要繼續判斷ViewHolder類型。其實這時候Adapter本身要傳的ViewHolder范型其實已經沒什么意義了。
這還不是最主要的,如果項目復雜了,大家用RecyclerView多了,會發現,不同界面經常會遇到復用ViewHolder的情況。經常會遇到界面0是ViewHolder0,ViewHolder2,ViewHolder4的組合。界面1是ViewHolder2,ViewHolder3的組合,界面2是ViewHolder0,ViewHolder4的組合。這時候我們是每個界面都寫一個Adapter嗎?通常大家都會這么處理,寫的多了會發現,Adapter在寫大量重復代碼。那怎么辦?
主要是基于這種需求。所以需要用一種優雅的方式解決Adapter需要處理多個ViewHolder,而且還需要ViewHolder在不同界面能非常簡單方便的被復用。
思路
我們要做什么?我們要做的就是把盡量多的重復邏輯封裝起來,避免重復勞動。另外就是盡可能讓復用簡單,屬于誰的代碼就讓誰處理。
既然Adapter存在綁定多個不同ViewHodler的情況,那我們最好有個通用的Adapter,這個Adapter能處理任意多個不同ViewHolder的create和bind,然后對應的界面自己去注冊自己需要的ViewHolder,你需要什么ViewHolder你就注冊什么ViewHolder。
我們現在重點關注Adapter的onCreateViewHolder和onBindViewHolder方法。
onCreateViewHolder是用來創建ViewHolder的,參數只有ViewGroup和ViewType,ViewGroup是讓我們用來填充ViewHolder進去的。實際能用來區分不同ViewHolder的就只有ViewType了。
那ViewType是從哪里來的?是從getItemViewType來的。getItemViewType可以通過position拿到我們的數據源,然后通過判斷數據源的類類型來判斷它是什么ViewType。
好,現在我們需要一個Adapter必定有的保存數據的數據源。這里我們選用List(注意必須是個有序List,而且這個List會被經常讀取數據,ArrayList讀取效率非常高,所以這里用ArrayList是最好的選擇。)。因為我們需要綁定多個ViewHolder,所以這里List的范型,需要是Object。就是支持任意類型。
回到getItemViewType,從getItemViewType的postion,我們拿到了List中保存的Data,那我們怎么知道這個Data要被綁定到什么ViewHolder上呢?所以需要有個register方法來注冊ViewHolder。那這個方法參數是什么?是ViewHolder和ViewHolder對應的Data。又因為我們ViewHolder是被onCreateViewHolder時候創建的,所以這個肯定是不能傳實例的。Data也不能是實例,Data應該是被調用處(通常是Http/Https Api返回)創建實例。所以這里兩個參數應該都是類類型。
public void register(Class<?> dataType, Class<? extends ViewHolder> viewType)
好了,我們把注冊的類類型保存在一個Map中。用來記錄每個數據類型對應的ViewHolder。又因為getItemViewType返回的是個int來區分ViewType,所以我們需要把Data的類類型映射為int,怎么映射?非常簡單用hashCode。所以這個紀錄data與View綁定關系的Map的key是個Int保存Data類類型的hashCode,value是ViewHolder的類類型。
再回到onCreateViewHolder方法,通過傳過來的int viewType,我們去我們保存映射關系的mBindMap中拿到這個ViewType對應的ViewHolder的類類型。然后我們就可以通過反射來創建ViewHolder對象了。
這里注意還有個問題。通常我們寫ViewHolder可能都會寫成下面這樣:
public static class SimpleViewHolder extends RecyclerView.ViewHolder{
public SimpleViewHolder(View itemView) {
super(itemView);
}
}
然后在onCreateViewHolder中通過類似下面的代碼創建SimpleViewHolder。
View view = LayoutInflater.from(context).inflate(R.layout.view_holder_simple,parent,false);
ViewHolder viewHodler = new SimpleViewHolder(view);
其實個人認為這是非常不好的寫法。為什么要讓調用者來創建屬于SimpleViewHolder的itemView?誰的事情交給誰做。SimpleViewHolder應該對應什么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就完全不關心SimpleViewHolder應該對應什么View,這個應該讓SimpelViewHolder自己決定。
回到前面onCreateViewHolder創建ViewHolder的地方。這時候我們要約定好,被注冊的ViewHolder必須有個參數為ViewGroup的構造方法。
然后繼續下一步onBindViewHolder,onBindViewHolder兩個參數,ViewHolder實例和positon(Data實例),我們要做的就是把Data設置到ViewHolder上,一個ViewHolder該如何綁定數據,肯定是ViewHolder自身最清楚,所以這里ViewHolder必須有個綁定數據的方法。這里我們需要抽象一個ViewHolder的基類,所有ViewHolder都必須繼承這個ViewHolder,然后這個ViewHodler基類,至少有個綁定數據的方法,我們暫時取名叫bindData。
好到這里我們基本就完成了,不同ViewType綁定的處理。
梳理下思路:
第一步:register注冊需要的ViewHolder和ViewHolder對應的數據Data。
第二步:getViewType返回Data對應的viewType
第三步:onCreateViewHolder通過ViewType拿到對應的ViewHolder類類型,通過反射創建ViewHolder實例。
第四步:拿到postion對應的數據,設置給ViewHolder。
思路有了剩下的就是填充代碼了。
Head Content和Foot的處理
在實際寫的時候考慮到我們經常會遇到需要區分Head Content和Foot的情況。我們前面那些關于不同ViewType的處理其實跟Head Content Foot是不沖突的。為了方便擴展實現。所以實際寫的時候抽象了一個AbsMultiTypeAdapter出來,AbsMultiTypeAdapter處理了不同Type的創建和綁定。但是AbsMultiTypeAdapter不關心數據源,所有需要調用數據源的方法(包含data的方法,比如addData,removeData)全部改為抽象方法,讓子類去實現。這樣子類可以根據需要來實現帶有Head Content和Foot的數據源。
在MultiTypeAdapterImpl中實際實現了完整的MultiTypeAdapter的功能。MultiTypeAdapterImpl實現了任意多個類型的Adapter。具體實現方式就是把之前我們習慣的數據源從List變成List套List的List<List<Object>>,外層List保存不同的Type,內層List是實際對應Type包含的數據。然后就是處理Postion與Type Offset的相互轉換了。詳細的不再細說。
最終效果
上面說了那么多,那最終實際效果是什么?我們實現了一個MultiTypeAdapterImpl。它支持任意的ViewHolder以及它們的組合。
使用時候第一步必須通過register方法注冊你需要的ViewHolder和這個ViewHolder對應的數據類型。當然ViewHolder必須繼承AbsViewHolder。然后就可以通過addItem方法給Adapter添加數據了,剩下的itemView createViewHolder bindViewHolder,MultiTypeAdapterImpl會幫你處理。
相當于你的Activity里寫的代碼非常少。只有register和addItem。ViewHolder復用非常輕松簡單。