Android的Kotlin秘方(II)(RecyclerView 和 DiffUtil)

天下之事常成于困约,而败于奢靡。这篇文章主要讲述Android的Kotlin秘方(II):RecyclerView 和 DiffUtil相关的知识,希望能为你提供帮助。
作者:Antonio Leiva
时间:Sep 12, 2016
原文链接:http://antonioleiva.com/recyclerview-diffutil-kotlin/
 
如你所知,在【支持库24(the Support Library 24)】中包括一个新的、适用、方便的类:DiffUtil,这使你摆脱对单元改变和更新它们的无聊和易出错。
 
如果你还不了解它,可以阅读Nicola Despotoski的这篇好文章了解它。这篇文章解释怎样容易处理它。
 
实际上,java语言引入许多模板,而我决定研究是用Kotlin怎样实现。
 
例子
我创建一个小APP(可以在GitHub下载)作为例子,它从一个有10项的列表中选择项目,用于下一次RecycerView。

Android的Kotlin秘方(II)(RecyclerView 和 DiffUtil)

文章图片

 
这样,从一次迭代到下次,有些被显示,有些消失,而有时整个全部更新。
 
如果你知道RecyclerView是怎样工作的,你就知道在它的适配器怎样通知那些改变,这就需要这三个方法:
  • notifyItemChanged
  • notifyItemInserted
  • notifyItemRemoved
以及它们对应的Range变化。
 
DiffUtil类将我们做所有的计算,且调用要求的notify方法。
 
原始实现方法
首次迭代,我们是从“ 提供者” 那里获得这些项目,让适配器通知变化(这即使不是最好的代码,而可以很快的理解为什么这样做):
1 private fun fillAdapter() { 2val oldItems = adapter.items 3adapter.items = provider.generate() 4adapter.notifyChanges(oldItems, adapter.items) 5 }

【Android的Kotlin秘方(II)(RecyclerView 和 DiffUtil)】 
简单:我保存前面项目,产生新的项目,对适配器说notifyChanges,而用DiffUtil方法是这样:
1 fun notifyChanges(old: List< Content> , new: List< Content> ) { 2val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() { 3override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 4return old[oldItemPosition].id == new[newItemPosition].id 5} 6 7override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 8return old[oldItemPosition] == new[newItemPosition] 9} 10 11override fun getOldListSize() = old.size 12 13override fun getNewListSize() = new.size 14}) 15 16diff.dispatchUpdatesTo(this) 17 }

 
由于大部分代码都是基于模板,这实在是令人讨厌,但是稍后还是要返回了。
 
现在,如你所见在设置新的一组项目后我调用notifyChanges。那我们为什么不委托那些行为?
 
用委托使通知更简单
如果我们知道每次一组项目改变时进行通知,那么仅需要用Delegates.observer,那么代码就非常棒:
 
这个activity就非常简单了:
1 private fun fillAdapter() { 2adapter.items = provider.generate() 3 }

 
“ 观察者” 看上去就非常不错:
1 class ContentAdapter():RecyclerView.Adapter< ContentAdapter.ViewHolder> () { 2 3var items: List< Content> by Delegates.observable(emptyList()) { 4prop, old, new -> 5notifyChanges(old, new) 6} 7... 8 }

 
太棒了!但是,这还可以更好。
 
用扩展函数提升适配器的能力
 
NotityChanges的大多数代码都是模式化的。如果用数据类,我们就需要实现判断两个项目是否相同的方法,即使它们的内容不同。
 
在这个例子中,识别的方法是id。
 
这样,我为这个适配器创建一个扩展函数,它将为我们做大部分困难的工作:
1 fun < T> RecyclerView.Adapter< *> .autoNotify(old: List< T> , new: List< T> , compare: (T, T) -> Boolean) { 2val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() { 3 4override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 5return compare(old[oldItemPosition], new[newItemPosition]) 6} 7 8override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 9return old[oldItemPosition] == new[newItemPosition] 10} 11 12override fun getOldListSize() = old.size 13 14override fun getNewListSize() = new.size 15}) 16 17diff.dispatchUpdatesTo(this) 18 }

 
这个函数接收两组项目,和另一个函数。这最后参数将在areItemsTheSame中使用,以确定两组项目是否相同。
 
现在调用是这样了:
1 var items: List< Content> by Delegates.observable(emptyList()) { 2prop, old, new -> 3autoNotify(old, new) { o, n -> o.id == n.id } 4 }

 
组合使用
我能理解你非常不喜欢前面的解决方案。而在特定情况下,你不要所有适配器都使用它。
 
但是,有一个替换方法:接口。
 
悲哀的是,在Kotlin预览上的某些位置上,接口不能扩展类(我非常希望在将来能够增加它)。这让你用扩展类的方法,强制类实现接口类型。
 
但是,我们将扩展函数移入接口内部,也能够获得类似的结果:
1 interface AutoUpdatableAdapter { 2 3fun < 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更好、更简单。
 
如果你需要尝试其它解决方案,从代码库获取,并删除一些特殊注释。
 

    推荐阅读