為RecyclerView打造通用Adapter 讓RecyclerView更加好用

發表於2016-04-13

一、概述

記得好久以前針對ListView類控制元件寫過一篇打造萬能的ListView GridView 介面卡,如今RecyclerView異軍突起,其Adapter的用法也與ListView類似,那麼我們也可以一步一步的為其打造通用的Adapter,使下列用法書寫更加簡單:

  • 簡單的資料繫結(單種Item)
  • 多種Item Type 資料繫結
  • 增加onItemClickListener , onItenLongClickListener
  • 優雅的新增分類header

二、使用方式和效果圖

在一步一步完成前,我們先看下使用方式和效果圖:

(1)簡單的資料繫結

首先看我們最常用的單種Item的書寫方式:

是不是相當方便,在convert方法中完成資料、事件繫結即可。

(2)多種ItemViewType

多種ItemViewType,正常考慮下,我們需要根據Item指定ItemType,並且根據ItemType指定相應的佈局檔案。我們通過MultiItemTypeSupport完成指定:

剩下就簡單了,將其作為引數傳入到MultiItemCommonAdapter即可。

貼個效果圖:

(3)新增分類header

其實屬於多種ItemViewType的一種了,只是比較常用,我們就簡單封裝下。

依賴正常考慮下,這種方式需要額外指定header的佈局,以及佈局中顯示標題的TextView了,以及根據Item顯示什麼樣的標題。我們通過SectionSupport物件指定:

3個方法,一個指定header的佈局檔案,一個指定佈局檔案中顯示title的TextView,最後一個用於指定顯示什麼樣的標題(根據Adapter的Bean)。

接下來就很簡單了:

這樣就完了,效果圖如下:

ok,看完上面簡單的介紹,相信你已經基本瞭解了,沒錯,和我上篇ListView萬能Adapter的使用方式基本一樣,並且已經封裝到同一個庫了,連結為:https://github.com/hongyangAndroid/base-adapter,此外還提供了ItemClick,ItemLongClick,新增EmptyView等支援。

說了這麼多,下面進入正題,看我們如何一步步完成整個封裝的過程。

三、通用的ViewHolder

RecyclerView要求必須使用ViewHolder模式,一般我們在使用過程中,都需要去建立一個新的ViewHolder然後作為泛型傳入Adapter。那麼想要建立通用的Adapter,必須有個通用的ViewHolder。

首先我們確定下ViewHolder的主要的作用,實際上是通過成員變數儲存對應的convertView中需要操作的字View,避免每次findViewById,從而提升執行的效率。

那麼既然是通用的View,那麼對於不同的ItemType肯定沒有辦法確定建立哪些成員變數View,取而代之的只能是個集合來儲存了。

那麼程式碼如下:

程式碼很簡單,我們的ViewHolder繼承自RecyclerView.ViewHolder,內部通過SparseArray來快取我們itemView內部的子View,從而得到一個通用的ViewHolder。每次需要建立ViewHolder只需要傳入我們的layoutId即可。

ok,有了通用的ViewHolder之後,我們的通用的Adapter分分鐘就出來了。

四、通用的Adapter

我們的每次使用過程中,針對的資料型別Bean肯定是不同的,那麼這裡肯定要引入泛型代表我們的Bean,內部通過一個List代表我們的資料,ok,剩下的看程式碼:

繼承自RecyclerView.Adapter,需要複寫的方法還是比較少的。首先我們使用過程中傳輸我們的資料集mDatas,和我們item的佈局檔案layoutId。

onCreateViewHolder時,通過layoutId即可利用我們的通用的ViewHolder生成例項。

onBindViewHolder這裡主要用於資料、事件繫結,我們這裡直接抽象出去,讓使用者去操作。可以看到我們修改了下引數,使用者可以拿到當前Item所需要的物件和viewHolder去操作。

那麼現在使用者的使用是這樣的:

看到這裡,爽了很多,目前我們僅僅寫了很少的程式碼,但是我們的通用的Adapter感覺已經初步完成了。

可以看到我們這裡通過viewholder根據控制元件的id拿到控制元件,然後再進行資料繫結和事件操作,我們還能做些什麼簡化呢?

恩,我們可以通過一些輔助方法簡化我們的程式碼,所以繼續往下看。

五、進一步封裝ViewHolder

我們的Item實際上使用的控制元件較多時候可能都是TextView,ImageView等,我們一般在convert方法都是去設定文字,圖片什麼的,那麼我們可以在ViewHolder裡面,寫上如下的一些輔助方法:

當然上面只給出了幾個方法,你可以把常用控制元件的方法都寫進去,並且在使用過程中不斷完善即可。

有了一堆輔助方法後,我們的操作更加簡化了一步。

ok,到這裡,我們的針對單種ViewItemType的通用Adapter就完成了,程式碼很簡單也很少,但是簡化效果非常明顯。

ok,接下來我們考慮多種ItemViewType的情況。

六、多種ItemViewType

多種ItemViewType,一般我們的寫法是:

  • 複寫getItemViewType,根據我們的bean去返回不同的型別
  • onCreateViewHolder中根據itemView去生成不同的ViewHolder

如果大家還記得,我們的ViewHolder是通用的,唯一依賴的就是個layoutId。那麼上述第二條就變成,根據不同的itemView告訴我用哪個layoutId即可,生成viewholder這種事我們通用adapter來做。

於是,引入一個介面:

可以很清楚的看到,這個介面實際就是完成我們上述的兩條工作。使用者在使用過程中,通過實現上面兩個方法,指明不同的Bean返回什麼itemViewType,不同的itemView所對應的layoutId.

ok,有了上面這個介面,我們的引數就夠了,下面開始我們的MultiItemCommonAdapter的編寫。

幾乎沒有幾行程式碼,感覺簡直不需要消耗腦細胞。getItemViewType使用者的傳入的MultiItemTypeSupport.getItemViewType完成,onCreateViewHolder中根據MultiItemTypeSupport.getLayoutId返回的layoutId,去生成ViewHolder即可。

ok,這樣的話,我們的多種ItemViewType的支援也就完成了,一路下來感覺還是蠻輕鬆的~~~

最後,我們還有個新增分類的header,為什麼想起來封裝這個呢,這個是因為我看到了這麼個專案:https://github.com/ragunathjawahar/simple-section-adapter,這個專案給了種類似裝飾者模式的方法,為ListView新增了header,有興趣可以看下。我想我們的RecylerView也來個吧,不過我們這裡直接通過繼承Adapter完成。

七、新增分類Header

話說新增分類header,其實就是我們多種ItemViewType的一種,那麼我們需要知道哪些引數呢?

簡單思考下,我們需要:

  1. header所對應的佈局檔案
  2. 顯示header的title對應的TextView
  3. 顯示的title是什麼(一般肯定根據Bean生成)

ok,這樣的話,我們依然引入一個介面,用於提供上述3各引數

方法名應該很明確了,這裡引入泛型,對應我們使用時的資料型別Bean。

剛才也說了我們的分類header是多種ItemViewType的一種,那麼直接繼承MultiItemCommonAdapter實現。

根據我們之前的程式碼,使用MultiItemCommonAdapter,需要提供一個MultiItemTypeSupport,我們這裡當然也不例外。可以看到上述程式碼,我們初始化了成員變數headerItemTypeSupport,分別對getLayoutIdgetItemViewType進行了實現。

  • getLayoutId如果type是header型別,則返回mSectionSupport.sectionHeaderLayoutId();否則則返回mLayout.
  • getItemViewType根據位置判斷,如果當前是header所在位置,返回header型別常量;否則返回1.

ok,可以看到我們構造方法中呼叫了findSections(),主要為了儲存我們的title和對應的position,通過一個MapmSections來儲存。

那麼對應的getItemCount()方法,我們多了幾個title肯定總數會增加,所以需要複寫。

onBindViewHolder中我們有一行重置position的程式碼,因為我們的position變大了,所以在實際上繫結我們資料時,這個position需要還原,程式碼邏輯見getIndexForPosition(position)

最後一點就是,每當我們的資料發生變化,我們的title集合,即mSections就可能會發生變化,所以需要重新生成,本來準備複寫notifyDataSetChanged方法,在裡面重新生成,沒想到這個方法是final的,於是利用了registerAdapterDataObserver(observer);,在資料發生變化回撥中重新生成,記得在onDetachedFromRecyclerView裡面對註冊的observer進行解註冊。

ok,到此我們的增加Header就結束了~~

恩,上面是針對普通的Item增加header的程式碼,如果是針對多種ItemViewType呢?其實也很簡單,這種方式需要傳入MultiItemTypeSupport。那麼對於headerItemTypeSupport中的getItemViewType等方法,不是header型別時,交給傳入的MultiItemTypeSupport即可,大致的程式碼如下:

那麼這樣的話,今天的部落格就結束了,有幾點需要說明下:

本來是想接著以前的萬能Adapter後面寫,但是為了本文的獨立和完整性,還是儘可能沒有去依賴上篇部落格的內容了。

此外,文章最後給出的開原始碼與上述程式碼存在些許的差異,因為開源部分原始碼整合了ListView,RecyclerView等,而本文上述程式碼完全針對RecyclerView進行編寫。

對於ItemClick,ItemLongClick的程式碼就不贅述了,其實都是通過itemView.setXXXListener完成,詳細的參考程式碼即可。

更多詳細的內容以及程式碼,參見https://github.com/hongyangAndroid/base-adapter

相關文章