DiffUtil 在嵌套的 recyclerview Kotlin 中不起作用
Posted
技术标签:
【中文标题】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 和 var
s 的过度使用,并简化您的 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 这个网站并不是为了让人们看你的问题。你不应该问特定的人。这不是一个消息传递网站。 对不起?我再也不会这样做了。以上是关于DiffUtil 在嵌套的 recyclerview Kotlin 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章
ViewHolder 中的嵌套 RecyclerView 破坏了折叠工具栏布局
DiffUtil 和 registerAdapterDataObserver
如何使用 DiffUtil 更新 RecyclerView 适配器