十秒鐘搞定 RecyclerView 資料繫結

lypeer發表於2016-09-06

前言

在上一個專案裡有很多很多很多很多的RecyclerView,然後我需要寫很多很多很多很多的Adapter和Viewholder——多倒沒問題,但是裡面有很多重複的程式碼這就不能忍了!每一個Adapter和ViewHolder其實做的事情非常的像:檢視繫結,資料繫結,點選事件分發。還有啥?既然它們做的事情都一樣,為啥我們還要傻傻的繼續寫著重複的程式碼?

正文

BaseAdapter

通常我們要建立一個RecyclerView.Adapter是怎麼做的?

  • 接收一個資料列表
  • 重寫getItemCount()方法,確定Item的個數
  • 重寫onCreateViewHolder()方法,繫結Layout,新建一個我們自己寫的RecyclerView.ViewHolder
  • 重寫onBindViewHolder()方法,進行資料和檢視繫結
  • 由於RecyclerView沒有寫點選事件,把點選事件分發出去

基本上就是這個套路,或者再加一個refreshData()的方法——傳新的資料進來然後notifyDataSetChanged()。基於這些點,我寫了一個BaseAdapter基類:

它的子類在繼承它的時候需要指定泛型的具體型別,因為不同的Item也許其資料型別並不一樣,這樣就可以適應更多的Item。另外,其中提到了一個介面OnItemClickListener,這個介面很簡單:

在使用它的時候同樣需要使用泛型——原因和上面一樣。

通過上面的BaseAdapter,我們把很多的共有操作都封裝在了基類裡面,而它的子類只需要根據需要新建不同的ViewHolder就行了——當然,這個viewHolder必須繼承自BaseViewHolder,而BaseViewHolder是什麼下面會有詳細講解。接下來是一個例子,假設我們現在在一個介面要有一個RecyclerView,它的每個Item的資料是一個String值,那麼怎麼使用我們的BaseAdapter簡化開發過程呢?

是的,你沒有看錯!就只有這麼幾行程式碼!5秒完成!驚喜麼?!

你只需要新建一個SampleAdapter繼承自BaseAdapter,然後指定其泛型為String,再return new SampleViewHolder(context, parent) , 就完成了整個操作。

但是,有些讀者也許會有疑惑:也許我們需要在SampleViewHolder裡面做很多的操作呢?那豈不是隻是把程式碼轉換了一個地方而已,實質上並沒有什麼優化之處。

然而並不是。

BaseViewHolder

我一直認為,將ViewHolder寫在Adapter裡面是挺不明智的一個做法。這樣的話Adapter這個類的職責太重了,它做得事情太多了,從資料接收到介面繫結,從控制元件初始化到點選事件分發,它簡直什麼都做完了。而這樣是很不好的。很輕易的,一個比較複雜的RecyclerView的Adapter的程式碼就能達到成百上千行,這很可怕,這意味著這個類將變得冗雜且難以維護。那麼怎麼避免這種情況發生呢?我選擇了將ViewHolder分離,同時加重ViewHolder的職責,使它們能比較均衡。

直接看BaseViewHolder的程式碼:

BaseViewHolder同樣採用了泛型,以適應不同的資料型別。同時,我在BaseViewHolder裡面使用了ButterKnife來簡化程式碼,從此再也不用反反覆覆的findViewById了。

另外,大家可以看到BaseViewHolder裡面的構造方法中傳入了三個引數,但是在上面BaseAdapter的例子裡面SampleViewHolder的構造方法我們卻只傳入了它的前兩個構造引數,而第三個引數layoutRes並沒有傳進去,這是怎麼回事呢?是上面寫錯了麼?當然不是。BaseViewHolder的構造方法中有三個傳參是因為它需要三個傳參,而它的子類只有兩個傳參是因為它只能有兩個傳參。_BaseViewHolder必須要滿足它的super構造,所以必須要有那三個引數,而它的子類如果那三個引數都是由外界傳進來的,那麼它怎麼進行鍼對那個佈局進行特異化的操作?它必須在類裡面顯式的指定Layout ID_——這其實是我很想優化的一個地方,因為這樣的話子類繼承BaseViewHolder之後還要修改它的構造方法,這是比較讓人不省心的,但是目前還沒有想到什麼好點子來優雅地優化它。

BaseViewHolder裡面有兩個看起來很像的方法:setData()和bindData(),然而實際上除了傳參相同,它們其他方面根本完全不一樣。setData()方法是一個public的方法,可以由BaseViewHolder的子類的物件呼叫,其作用是從外部傳入資料,以供ViewHolder所hold的那個view初始化,它可以說是一座傳輸資訊的橋樑;而getData()是一個抽象的方法,它的具體實現在每一個BaseViewHolder的子類中,這些子類在這個方法裡面進行控制元件的繫結和初始化,以及對控制元件的點選事件的處理等等。

說到點選事件的處理,它的子類應該怎麼完成這件事呢?通過OnItemClickListener。我們可以藉助於OnItemClickListener的介面回撥來將需要處理的點選事件傳遞到外界,然後由外界進行處理。那麼如果有多個控制元件的點選事件需要處理怎麼辦?不用擔心,因為在OnItemClickListener的onClick方法中你需要傳入點選的控制元件的id,這樣一來就可以在外界進行對傳入id的判斷,從而針對不同的id執行不同的點選事件了。

接下來通過一個例子來看下具體怎麼用,就是上面的那個SampleViewHolder吧:

同樣很簡單方便快捷清晰。但是還是有幾點值得注意的地方。首先,不能忘了修改構造方法,顯式的在super裡面指定Layout ID,不然下一步都沒法做,那就懵逼了。另外,在呼叫listener.onClick()方法的時候必須進行listener的驗空——因為listener真的有可能為空!這樣的話非常容易產生空異常導致程式崩潰。

賓果,就這樣,SampleViewHolder就完成了,同樣幾乎不費吹灰之力。

結語

這篇博文的目的是分享一些我總結出來的東西,希望能讓大家的開發加速那麼一點點。當然,這裡面也許還有我沒發現的bug什麼的,如果大家在使用的過程中發現了問題請不要客氣,狠狠地砸給我吧!

最後還是再總結一下在有了BaseAdapter和BaseViewHolder的情況下怎麼最快的搞定RecyclerView的那一套:

  • ViewHolder相關
    • 新建 XXXViewHolder 繼承自BaseViewHolder,指定泛型型別(也就是Item中資料的資料型別)。
    • 刪掉構造方法中的layoutRes引數,在super裡面顯式指定Layout ID。
    • 用ButterKnife繫結控制元件。
    • 在bindData()方法中完成控制元件的初始化以及點選事件的傳遞(別忘了listener的驗空)
  • Adapter相關
    • 新建 XXXAdapter 繼承自BaseAdapter,指定泛型型別(也就是Item中資料的資料型別)。
    • return new XXXViewHolder(context, parent);
  • 外界相關
    • 繫結RecyclerView,新建XXXAdapter。
    • 呼叫 BaseAdapter.refreshData()方法傳入資料列表。
    • 如果有對點選事件處理的需求,則呼叫BaseAdapter.setOnClickListener()方法。

目前我只做了針對RecyclerView中單個的Item的BaseAdapter,但是BaseViewHolder使可以通用的,並且其在多Item下也可以大大的簡化Adapter的體積。

BaseAdapter和BaseViewHolder以及demo的原始碼 在這裡

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

十秒鐘搞定 RecyclerView 資料繫結

相關文章