• 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。

     

    这样,从一次迭代到下次,有些被显示,有些消失,而有时整个全部更新。

     

    如果你知道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更好、更简单。

     

    如果你需要尝试其它解决方案,从代码库获取,并删除一些特殊注释。

     

  • 相关阅读:
    jquery的get方式发送AJAX请求
    原生JS发送AJAX请求
    正则表达式(二)
    正则表达式(一)
    旅游攻略-北京三日游攻略(已实践)
    边旅游边赚钱的噱头,这是一种传销!
    hdu 1106 排序(水题)
    hdu 1258 Sum It Up(dfs+去重)
    hdu 1455 Sticks(dfs+剪枝)
    解决“LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏”问题
  • 原文地址:https://www.cnblogs.com/figozhg/p/5914667.html
Copyright © 2020-2023  润新知