做者: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是怎样工做的,你就知道在它的适配器怎样通知那些改变,这就须要这三个方法:接口
以及它们对应的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更好、更简单。
若是你须要尝试其它解决方案,从代码库获取,并删除一些特殊注释。