Android的Kotlin祕方(II):RecyclerView 和 DiffUtil

做者:Antonio Leivagit

時間:Sep 12, 2016github

原文連接:http://antonioleiva.com/recyclerview-diffutil-kotlin/ide

 

如你所知,在【支持庫24(the Support Library 24)】中包括一個新的、適用、方便的類:DiffUtil,這使你擺脫對單元改變和更新它們的無聊和易出錯。函數

 

若是你還不瞭解它,能夠閱讀Nicola Despotoski的這篇好文章瞭解它。這篇文章解釋怎樣容易處理它。this

 

實際上,Java語言引入許多模板,而我決定研究是用Kotlin怎樣實現。spa

 

例子code

我建立一個小APP(能夠在GitHub下載)做爲例子,它從一個有10項的列表中選擇項目,用於下一次RecycerView。server

 

這樣,從一次迭代到下次,有些被顯示,有些消失,而有時整個所有更新。blog

 

若是你知道RecyclerView是怎樣工做的,你就知道在它的適配器怎樣通知那些改變,這就須要這三個方法:接口

  • notifyItemChanged
  • notifyItemInserted
  • notifyItemRemoved

以及它們對應的Range變化。

 

DiffUtil類將咱們作全部的計算,且調用要求的notify方法。

 

原始實現方法

首次迭代,咱們是從「提供者」那裏得到這些項目,讓適配器通知變化(這即便不是最好的代碼,而能夠很快的理解爲何這樣作):

1 private fun fillAdapter() {
2     val oldItems = adapter.items
3     adapter.items = provider.generate()
4     adapter.notifyChanges(oldItems, adapter.items)
5 }

 

簡單:我保存前面項目,產生新的項目,對適配器說notifyChanges,而用DiffUtil方法是這樣:

 1 fun notifyChanges(old: List<Content>, new: List<Content>) {
 2     val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
 3         override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
 4             return old[oldItemPosition].id == new[newItemPosition].id
 5         }
 6    
 7         override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
 8             return old[oldItemPosition] == new[newItemPosition]
 9         }
10 
11         override fun getOldListSize() = old.size
12        
13         override fun getNewListSize() = new.size
14     })
15 
16     diff.dispatchUpdatesTo(this)
17 }

 

因爲大部分代碼都是基於模板,這實在是使人討厭,可是稍後仍是要返回了。

 

如今,如你所見在設置新的一組項目後我調用notifyChanges。那咱們爲何不委託那些行爲?

 

用委託使通知更簡單

若是咱們知道每次一組項目改變時進行通知,那麼僅須要用Delegates.observer,那麼代碼就很是棒:

 

這個activity就很是簡單了:

1 private fun fillAdapter() {
2     adapter.items = provider.generate()
3 }

 

「觀察者」看上去就很是不錯:

1 class ContentAdapter():RecyclerView.Adapter<ContentAdapter.ViewHolder>() {
2 
3     var items: List<Content> by Delegates.observable(emptyList()) {
4         prop, old, new ->
5                 notifyChanges(old, new)
6     }
7      ...
8 }

 

太棒了!可是,這還能夠更好。

 

用擴展函數提高適配器的能力

 

NotityChanges的大多數代碼都是模式化的。若是用數據類,咱們就須要實現判斷兩個項目是否相同的方法,即便它們的內容不一樣。

 

在這個例子中,識別的方法是id。

 

這樣,我爲這個適配器建立一個擴展函數,它將爲咱們作大部分困難的工做:

 1 fun <T> RecyclerView.Adapter<*>.autoNotify(old: List<T>, new: List<T>, compare: (T, T) -> Boolean) {
 2     val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
 3 
 4         override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
 5             return compare(old[oldItemPosition], new[newItemPosition])
 6         }
 7 
 8         override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
 9             return old[oldItemPosition] == new[newItemPosition]
10         }
11 
12         override fun getOldListSize() = old.size
13 
14         override fun getNewListSize() = new.size
15     })
16 
17     diff.dispatchUpdatesTo(this)
18 }

 

這個函數接收兩組項目,和另外一個函數。這最後參數將在areItemsTheSame中使用,以肯定兩組項目是否相同。

 

如今調用是這樣了:

1 var items: List<Content> by Delegates.observable(emptyList()) {
2     prop, old, new ->
3             autoNotify(old, new) { o, n -> o.id == n.id }
4 }

 

組合使用

我能理解你很是不喜歡前面的解決方案。而在特定狀況下,你不要全部適配器都使用它。

 

可是,有一個替換方法:接口。

 

悲哀的是,在Kotlin預覽上的某些位置上,接口不能擴展類(我很是但願在未來可以增長它)。這讓你用擴展類的方法,強制類實現接口類型。

 

可是,咱們將擴展函數移入接口內部,也可以得到相似的結果:

1 interface AutoUpdatableAdapter {
2 
3     fun <T> RecyclerView.Adapter<*>.autoNotify(old: List<T>, new: List<T>, compare: (T, T) -> Boolean) {
4         ...
5     }
6 }

 

適配器只須要實現這個接口:

1 class ContentAdapter() : RecyclerView.Adapter<ContentAdapter.ViewHolder>(), AutoUpdatableAdapter {
2     ....
3 }

 

這就是全部代碼,其它保持不變。

 

結論

有幾個方法用DiffUtls使代碼看起比Java更好、更簡單。

 

若是你須要嘗試其它解決方案,從代碼庫獲取,並刪除一些特殊註釋。

相關文章
相關標籤/搜索