RecyclerView多類型狀態下寫個優雅的通用Adapter

相關源碼和示例

源碼參考:
https://github.com/aesean/ApiDemo/blob/master/app/src/main/java/com/aesean/apidemo/widget/recyclerview/AbsMultiTypeAdapter.java

https://github.com/aesean/ApiDemo/blob/master/app/src/main/java/com/aesean/apidemo/widget/recyclerview/MultiTypeAdapterImpl.java

使用示例:
https://github.com/aesean/ApiDemo/blob/master/app/src/main/java/com/aesean/apidemo/activity/recyclerview/MultiTypeRecyclerViewActivity.java

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復用非常輕松簡單。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內容