DiffUtil 在嵌套的 recyclerview Kotlin 中不起作用

     2023-04-18     139

关键词:

【中文标题】DiffUtil 在嵌套的 recyclerview Kotlin 中不起作用【英文标题】:DiffUtil Not working in nested recyclerview Kotlin 【发布时间】:2022-01-02 19:30:56 【问题描述】:

我有两个回收站视图。在我使用notifyDataSetChanged 之前,我的视图不会更新。我要求类似类型的issue,但这次我有Github 链接。所以请看看并向我解释我做错了什么。谢谢

MainActivity.kt

package com.example.diffutilexample

import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.example.diffutilexample.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() 

    private val viewModel by viewModels<ActivityViewModel>()
    private lateinit var binding: ActivityMainBinding
    private var groupAdapter: GroupAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setupViewModel()
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        viewModel.fetchData()

        binding.button.setOnClickListener 
            viewModel.addData()
        
    

    private fun setupViewModel() 
        viewModel.groupListLiveData.observe(this) 
            if (groupAdapter == null) 
                groupAdapter = GroupAdapter()
                binding.recyclerview.adapter = groupAdapter
            
            groupAdapter?.submitList(viewModel.groupList?.toMutableList())
            binding.recyclerview.post 
                groupAdapter?.notifyDataSetChanged()
            
        
    

ActivityViewModel.kt

package com.example.diffutilexample

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class ActivityViewModel(app: Application) : AndroidViewModel(app) 

    var groupListLiveData: MutableLiveData<Boolean> = MutableLiveData()
    var groupList: ArrayDeque<Group>? = null
        set(value) 
            field = value
            groupListLiveData.postValue(true)
        
    var value = 0

    fun fetchData() 
        viewModelScope.launch 
            val response = ApiInterface.create().getResponse()

            groupList = groupByData(response.abc)
        
    

    private fun groupByData(abc: List<Abc>?): ArrayDeque<Group> 
        val result: ArrayDeque<Group> = groupList ?: ArrayDeque()

        abc?.iterator()?.forEach  item ->
            val key = GroupKey(item.qwe)
            result.addFirst(Group(key, mutableListOf(item)))
        
        return result
    

    fun addData() 
        groupList?.let  lastList ->
            val qwe = Qwe("Vivek $value++", "Modi")
            val item = Abc(type = "Type 1", "Adding Message", qwe)
            val lastGroup = lastList[0]
            lastGroup.list.add(item)
            groupList = lastList
        
    

请在 Github 链接中找到整个代码。我附在上面

【问题讨论】:

在此行上方记录groupList groupAdapter?.submitList(viewModel.groupList?.toMutableList())。每次更改时检查是否记录了正确的数据。 @ArpitShukla 我记录了它,我的数据正在添加到列表中,如果我使用 notifyDataSetChanged 工作正常 notifyDataSetChanged 无法更改日志输出。您是否获得了正确的日志输出(有或没有) notifyDataSetChanged? @ArpitShukla 是的,当我添加数据时,日志给了我正确的输出。有或没有)notifyDataSetChanged 我没有给出答案,而是质疑您为什么认为 DiffUtil 没有被触发?我敢打赌,调试代码的一种方法是在 areItemsTheSame()areContentsTheSame() 中添加日志记录点,以查看 DiffUtil 是否真的在尝试进行比较。如果您以 DiffUtil 认为没有任何改变的方式错误地实现了这些功能,那么显然 RecyclerView 将不会按您的预期显示更新的值。 【参考方案1】:

我没有对此进行调试,但是如果您消除对 MutableLists 和 vars 的过度使用,并简化您的 LiveData,您可能会消除您的错误。至少,它会帮助您找到问题所在。

MutableLists 和 DiffUtil 不能很好地配合使用!

例如Group的列表应该是只读的List:

data class Group(
    val key: GroupKey,
    val list: List<Abc?> = emptyList()
)

有一个 LiveData 只报告其他属性是否可用,这很令人费解。然后,您在这里和观察者中到处都在处理可空性,因此很难从空安全调用中判断何时将跳过某些代码。我会更改您的 LiveData 以直接发布只读列表。您可以使用emptyList() 来避免可为空的列表,也可以简化代码。

您也可以避免使用 ArrayDeque 公开展示您的内部工作原理。而且您在不必要地延迟加载 ArrayDeque,这导致不得不处理不必要的可空性。

class ActivityViewModel(app: Application) : AndroidViewModel(app) 

    private val _groupList = MutableLiveData<List<Group>>()
    val groupList: LiveData<List<Group>> get() = _groupList
    private val trackedGroups = ArrayDeque<Group>()
    private var counter = 0

    fun fetchData() 
        viewModelScope.launch 
            val response = ApiInterface.create().getResponse()
            addFetchedData(response.abc.orEmpty())
            _groupList.value = trackedGroups.toList() // new copy for observers
        
    

    private fun addFetchedData(abcList: List<Abc>) 
        for (item in abcList) 
            val key = GroupKey(item.qwe)
            trackedGroups.addFirst(Group(key, listOf(item)))
        
    

    fun addData() 
        if (trackedGroups.isEmpty())
            return // Might want to create a default instead of doing nothing?
        val qwe = Qwe("Vivek $counter++", "Modi")
        val item = Abc(type = "Type 1", "Adding Message", qwe)
        val group = trackedGroups[0]
        trackedGroups[0] = group.copy(list = group.list + item)

        _groupList.value = trackedGroups.toList() // new copy for observers
    

在您的 Activity 中,由于您的 GroupAdapter 没有依赖项,您可以在调用站点对其进行实例化,以避免处理延迟加载。并且您可以立即将其设置为onCreate() 中的RecyclerView。

由于 ViewModel 的变化,观察变得非常简单。

如果您在setupViewModel() 中执行的操作会立即更新视图,则会发生崩溃,因此您应该在调用setContentView() 之后移动它。

class MainActivity : AppCompatActivity() 

    private val viewModel by viewModels<ActivityViewModel>()
    private lateinit var binding: ActivityMainBinding
    private val groupAdapter = GroupAdapter()

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater).apply 
            setContentView(root)
            recyclerview.adapter = groupAdapter
            button.setOnClickListener 
                viewModel.addData()
            
        

        setupViewModel()
        viewModel.fetchData()
    

    private fun setupViewModel() 
        viewModel.groupList.observe(this) 
            groupAdapter.submitList(it)
        
    

您在 GroupAdapter 中的 DiffUtil.ItemCallback.areItemsTheSame 不正确。您只应该检查它们是否代表相同的项目,而不是它们的内容是否相同,因此不应该比较列表。

override fun areItemsTheSame(oldItem: Group, newItem: Group): Boolean 
    return oldItem.key == newItem.key

在 GroupViewHolder 中,每次反弹时,您都在为内部 RecyclerView 创建一个新适配器。这完全违背了使用 RecyclerView 的目的。您应该只创建一次适配器。

我预测,当视图被回收而不是更新时,嵌套列表中的更改会看起来很奇怪,因为它会为之前视图中的更改设置动画,这可能来自不同的项目.所以我们可能应该跟踪旧的项目键并在新键不匹配时避免动画。我认为这可以在submitList()回调参数中通过调用notifyDataSetChanged()在适配器中更新列表内容后运行,但我还没有测试过。

class GroupViewHolder(val binding: ItemLayoutBinding) : RecyclerView.ViewHolder(binding.root) 
    
    companion object 
        //...
    

    private val adapter = NestedGroupAdapter().also 
        binding.nestedRecyclerview.adapter = it
    

    private var previousKey: GroupKey? = null

    fun bindItem(item: Group?) 
        val skipAnimation = item?.key != previousKey
        previousKey = item?.key
        adapter.submitList(item?.list.orEmpty()) 
            if (skipAnimation) adapter.notifyDataSetChanged()
        
    

旁注:您的适配器的bindView 函数名称容易混淆。我只是将它们变成辅助构造函数,您可以将主构造函数设为私有。

class GroupViewHolder private constructor(private val binding: ItemLayoutBinding) :
    RecyclerView.ViewHolder(binding.root) 

    constructor(parent: ViewGroup) : this(
        ItemLayoutBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
    )

    //...

【讨论】:

感谢@Tenfour04 很好的回答.. bindItem 函数中的 item?.list.orEmpty() 是什么? 安全调用item,因为它可以为空。 orEmpty() 将可空列表引用转换为非空列表引用,如果它为空,则将空列表替换为空列表。这样,如果我们得到一个空白行项目,我们可以确保在回收视图中清除旧数据。 嘿,你能帮我解决这个问题吗issue 我从未使用过 ViewPager。【参考方案2】:

我不完全确定,我承认我没有深入研究过你的代码,这不是一个解决方案,但这可能会为你指明如何解决它的正确方向。

关于

groupAdapter?.submitList(viewModel.groupList?.toMutableList())

toMutableList() 确实复制了列表。但是列表中的每个对象都不是副本。如果您向原始列表中的对象添加内容,就像您在addData() 中所做的那样,实际上它也已经添加到适配器中的副本中。这就是为什么新的 submitList 不会将其识别为更改的原因,因为它实际上与 submitList 之前的相同。

据我了解,如果您提交的列表仅包含不可变对象,则使用 DiffUtil 效果最佳,因此不会发生此类错误。我之前遇到过类似的问题,解决方案也不是很简单。事实上,我不完全记得我当时是如何解决它的,但希望这能将你推向正确的方向。

【讨论】:

感谢您的回复。我尝试了每个solution,但它不起作用。 这就是我添加示例项目的原因。 嘿@IvoBeckers 你能看到这个issue @vivekmodi 这个网站并不是为了让人们看你的问题。你不应该问特定的人。这不是一个消息传递网站。 对不起?我再也不会这样做了。

Paging 3 - 如何在 PagingDataAdapter 完成刷新并且 DiffUtil 完成差异后滚动到 RecyclerView 的顶部?

】Paging3-如何在PagingDataAdapter完成刷新并且DiffUtil完成差异后滚动到RecyclerView的顶部?【英文标题】:Paging3-HowtoscrolltotopofRecyclerViewafterPagingDataAdapterhasfinishedrefreshingANDDiffUtilhasfinisheddiffing?【发布时间】:2021-04-2903:26:15【问题描述】... 查看详情

recyclerview—diffutil

参考技术ADiffUtil16年出来的,是为了我饿们你在更新列表数据时可以实现动画效果样子是这样的:基础部分我就不写了,大家看这里就行,写的挺清楚:本文例子:接下来我就来说说注意事项DiffUtil想出动画效果,必须给RecyclerView... 查看详情

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

】Epoxy库中的自动比较是基于DiffUtil吗?【英文标题】:IstheautomaticcomparisonintheEpoxylibrarybasedonDiffUtil?【发布时间】:2021-11-2818:03:13【问题描述】:我在寻找有关RecyclerView的信息时遇到了Epoxylibrary。Epoxy是一个使RecyclerView更易于使用... 查看详情

如何使用 DiffUtil 更新 RecyclerView 适配器

】如何使用DiffUtil更新RecyclerView适配器【英文标题】:HowtoupdateRecyclerViewadapterusingDiffUtil【发布时间】:2018-03-1714:58:19【问题描述】:每次我需要更新RecycleView中的警报时,我都会创建一个新的Adapter和一个新的RecycleView。不幸的是... 查看详情

如何在 Recyclerview 中正确使用 DiffUtil 更新数组列表?

】如何在Recyclerview中正确使用DiffUtil更新数组列表?【英文标题】:HowtoproperlyuseDiffUtilsinRecyclerviewtoupdatearraylist?【发布时间】:2021-12-0704:41:32【问题描述】:我正在为recyclerview实现diffutils以通知列表。在搜索过滤器时,recyclerviewd... 查看详情

android高性能列表:recyclerview+diffutil(代码片段)

...新notifyDataSetChanged()局部刷新实现调用代码准备工作创建MyDiffUtilCallback类继承DiffUtil.Callback抽象类MyAdpter类代码实现步骤总结通过log证实diffutil的局部刷新diffutil优化后台线程参考主线程参考diff更新优化后写法相关参考背景学习记... 查看详情

DiffUtil 和 registerAdapterDataObserver

】DiffUtil和registerAdapterDataObserver【英文标题】:DiffUtilandregisterAdapterDataObserver【发布时间】:2018-10-2612:28:51【问题描述】:我有一个具有DiffUtil功能的RecyclerView适配器。我想在DiffUtil完成其魔法时使用DataObserver通知片段。但是,看... 查看详情

diffutil和它的差量算法(代码片段)

DiffUtil和它的差量算法前言学习Myers'Diff算法是从DiffUtils源代码开始的,但DiffUtil和它的差量算法这篇却是文章是在写完Myers‘Diff之贪婪算法和Myers‘Diff之线性空间细化这两篇算法文章之后着手的。比较先需要学会算法才能... 查看详情

DiffUtil.ItemCallback - 定义为伴随对象还是类?

】DiffUtil.ItemCallback-定义为伴随对象还是类?【英文标题】:DiffUtil.ItemCallback-defineasacompanionobjectorasaclass?【发布时间】:2021-07-2821:50:39【问题描述】:我目前正在通过Udacity的KotlinAndroid开发人员计划学习Kotlin。有两个使用DiffUtil.Ite... 查看详情

如何为图像实现 DiffUtil

】如何为图像实现DiffUtil【英文标题】:HowtoimplementDiffUtilforimages【发布时间】:2022-01-0418:15:20【问题描述】:您好,我听说这个库DiffUtil提高了回收器视图的性能,因此我的回收器视图包含图像,我最好实现它,但我不知道如何... 查看详情

DiffUtil 重绘 ListAdapter Kotlin 中的所有项目

】DiffUtil重绘ListAdapterKotlin中的所有项目【英文标题】:DiffUtilredrawallitemsinListAdapterKotlin【发布时间】:2021-12-2409:03:05【问题描述】:我在AndroidKotlin中使用DiffUtil和ListAdapter。我在onResume方法中从服务器调用数据。当onResume调用每个... 查看详情

Recyclerview DiffUtil 项目更新

】RecyclerviewDiffUtil项目更新【英文标题】:RecyclerviewDiffUtilItemUpdate【发布时间】:2020-06-0409:06:56【问题描述】:我的recyclerview中有无限滚动,所以当有新数据时它会更新。我正在使用DiffUtil更新recyclerview中的数据。DiffUtil确实会更... 查看详情

DiffUtil 不刷新观察者调用android kotlin中的视图

】DiffUtil不刷新观察者调用androidkotlin中的视图【英文标题】:DiffUtilnotrefreshingviewinObservercallandroidkotlin【发布时间】:2021-12-1108:54:11【问题描述】:嘿,我正在使用带有ListAdapter的diffutil。列表的更新有效,但我只能通过滚动列表... 查看详情

带有 DiffUtil (ListAdapter) 的 RecyclerView 会阻塞 UI 线程

】带有DiffUtil(ListAdapter)的RecyclerView会阻塞UI线程【英文标题】:RecyclerViewwithDiffUtil(ListAdapter)blocksUIthread【发布时间】:2019-08-1111:34:02【问题描述】:我正在使用RecyclerView和ListAdapter(当列表被替换时,它使用AsyncListDiffer来计算和... 查看详情

Android DiffUtil::onBindViewHolder 返回从列表中移除的位置

】AndroidDiffUtil::onBindViewHolder返回从列表中移除的位置【英文标题】:AndroidDiffUtil::onBindViewHolderreturningthepositionremovedfromthelist【发布时间】:2020-06-2108:06:04【问题描述】:单击项目时,我试图从适配器中删除项目显示器做对了但是... 查看详情

如何将 DiffUtil.Callback 与具有页眉和页脚的 RecyclerView 一起使用?

】如何将DiffUtil.Callback与具有页眉和页脚的RecyclerView一起使用?【英文标题】:HowtouseDiffUtil.CallbackwithaRecyclerViewthathasheaderandfooter?【发布时间】:2018-09-1917:43:09【问题描述】:背景我正在开发一个具有RecyclerView的应用程序,您可... 查看详情

recyclerview 中的 Diffutil,如果添加了新项目,则使其自动滚动

】recyclerview中的Diffutil,如果添加了新项目,则使其自动滚动【英文标题】:Diffutilinrecycleview,makingitautoscrollifanewitemisadded【发布时间】:2017-09-1311:09:12【问题描述】:如果我们使用DiffUtil.Callback,然后做adapter.setItems(itemList);diff.dis... 查看详情

当视图更新 Android Kotlin 时,DiffUtil 不与 ListAdpater 一起使用

】当视图更新AndroidKotlin时,DiffUtil不与ListAdpater一起使用【英文标题】:DiffUtilNotworkingwithListAdpaterwhenviewisupdatingAndroidKotlin【发布时间】:2021-12-1009:18:10【问题描述】:嘿,我有Reyclerview和DiffUtill使用ListAdapter。我通过submitList函数... 查看详情