Recyclerview DiffUtil 项目更新

Posted

技术标签:

【中文标题】Recyclerview DiffUtil 项目更新【英文标题】:Recyclerview DiffUtil Item Update 【发布时间】:2020-06-04 09:06:56 【问题描述】:

我的 recyclerview 中有无限滚动,所以当有新数据时它会更新。我正在使用 DiffUtil 更新 recyclerview 中的数据。 DiffUtil 确实会更新数据,但只要有更新数据,recyclerview 就会滚动到顶部,看起来就像“使用 notifydatasetchanged()”。这是我的 DiffUtil 和用于更新数据的适配器。

class ProductDiffUtil(
    val oldProductList: List<ProductModel>, val newProductList: List<ProductModel>
) : DiffUtil.Callback() 

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean 
        return oldProductList[oldItemPosition].id == newProductList[newItemPosition].id
    

    override fun getOldListSize(): Int = oldProductList.size

    override fun getNewListSize(): Int = newProductList.size

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean 
        return oldProductList[oldItemPosition] == newProductList[newItemPosition]
    

    override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? 
        return super.getChangePayload(oldItemPosition, newItemPosition)
    

这是我更新数据的适配器

fun addProductList(productList: List<ProductModel>?) 
        val diffResult = DiffUtil.calculateDiff(ProductDiffUtil(this.productList, productList!!))
        this.productList.addAll(productList)
        diffResult.dispatchUpdatesTo(this)
    

请帮我解决这个问题。当我使用 notifyItemRangeChanged() 时它工作正常......所以我应该使用什么来更新 recyclerview 中的数据以获得最佳实践。

https://drive.google.com/open?id=1SapXW2wusmTpyGCRA9fa0aSLCYNL1fzN

【问题讨论】:

使用 notifyitemrangeinserted 方法。 我最初使用的是 notifyItemRangeInserted() 但 diffUtil 使列表更快,所以我正在尝试使用它。 DiffUtil.ItemCallback&lt;T&gt;() 代替DiffUtil.Callback 怎么样。 你是如何实现适配器的?你是使用分页库还是定制的? 【参考方案1】:

您只是将以前的内容与新项目进行比较,而不是与添加了所有项目的列表进行比较。

假设this.productList 当前是1,2,3,而新的productList4,5,6。当你运行时

DiffUtil.calculateDiff(ProductDiffUtil(this.productList, productList!!)

它会将1425 等进行比较,并得出结论:一切都发生了变化,并且没有添加新项目。 (注意:这是对 DiffUtil 算法的过度简化,但用于说明这一点)

如果你想使用 DiffUtil:

val oldList = ArrayList(productList)
this.productList.addAll(productList)
val diffResult = DiffUtil.calculateDiff(ProductDiffUtil(oldList, productList!!)
diffResult.dispatchUpdatesTo(this)

或者,由于您确切知道添加了多少项目以及在何处添加,只需使用 notifyItemRangeInserted 并避免复制:

val oldSize = this.productList.size
this.productList.addAll(productList)
notifyItemRangeInserted(oldSize, productList.size)

【讨论】:

fun addProductList(productList: List?) 此方法仅更新来自 api 的新列表数据。所以,如果我这样做,列表的大小将不会改变。例如,this.productlist 为 1,2,3,新产品列表为 4,5,6。它不会包含 1,2,3,4,5,6。我按照你说的试过了,但不符合我的需要。我怎样才能得到这个?你能告诉我这个例子或其他的吗? 啊,我明白了 - 从问题中并不清楚。我已经更新了我的答案以考虑到这一点 - 看看是否能解决你的问题。考虑到这种行为,这更有意义。 感谢您的回答。它帮助我解决了我的问题。抱歉,兄弟重播晚了。【参考方案2】:

考虑创建一个通用的 diffUtil 类,而不是为每个适配器创建它。

fun <T>diffList(oldList: List<T>, newList: List<T>, sameItem: (a: T, b: T) -> Boolean): DiffUtil.DiffResult 
val callback: DiffUtil.Callback = object : DiffUtil.Callback() 
    override fun getOldListSize(): Int 
        return oldList.size
    

    override fun getNewListSize(): Int 
        return newList.size
    

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean 
        return sameItem(oldList[oldItemPosition], newList[newItemPosition])
    

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean 
        return oldList[oldItemPosition] == newList[newItemPosition]
    


return DiffUtil.calculateDiff(callback) 

您可以像这样在适配器中使用它:

 fun setItems(products: List<Product>) 
    val oldList = productList
    productList = products
    diffList(oldList, products, sameItem =  a, b -> a.id == b.id ).dispatchUpdatesTo(this)

【讨论】:

【参考方案3】:

检查布局管理器是否已经设置并获取当前滚动位置。像这样:

var itemPostion= 0
if(myRecyclerView.layoutmanager != null)
  itemPostion = (myRecyclerView.layoutmanager as LinearLayoutManager)
.findFirstCompletelyVisibleItemPosition()

您可以在 GitHub 上查看此示例项目

【讨论】:

这不应该是必要的,他们不应该更换适配器。 @RyanMentley 它没有更换适配器。它恢复了他想要解决的上一个滚动位置,而不是在数据更新时滚动回顶部。 RecyclerView 在更新时默认不会滚动到顶部。这不应该是必要的。

以上是关于Recyclerview DiffUtil 项目更新的主要内容,如果未能解决你的问题,请参考以下文章

Epoxy库中的自动比较是基于DiffUtil吗?

RecyclerView — DiffUtil

Android开发Diffutils打造不一样的recyclerview

Android 高性能列表:RecyclerView + DiffUtil

Android 高性能列表:RecyclerView + DiffUtil

如何使用 DiffUtil 更新 RecyclerView 适配器