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

Posted

技术标签:

【中文标题】当视图更新 Android Kotlin 时,DiffUtil 不与 ListAdpater 一起使用【英文标题】:DiffUtil Not working with ListAdpater when view is updating Android Kotlin 【发布时间】:2021-12-10 09:18:10 【问题描述】:

嘿,我有 ReyclerviewDiffUtill 使用 ListAdapter。我通过 submitList 函数添加了元素。但是在更新列表视图时不会重绘元素。直到我再次使用 notifyDataSetChanged() 或设置 adapter 。那么 DiffUtill 的用例是什么?在列表和 reyclerview 中更新项目时重绘项目的正确方法是什么。

MainActivity

class MainActivity : AppCompatActivity() 

    private var list = mutableListOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
    private lateinit var binding: ActivityMainBinding
    var i = 0

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        Log.e("List", " $list")
        val intAdapter = IntAdapter()
        binding.recylerview.adapter = intAdapter
        intAdapter.submitList(list)
        binding.button.setOnClickListener 
            list.add(++i)
            intAdapter.submitList(list)
//            binding.recylerview.adapter = intAdapter
//            intAdapter.notifyDataSetChanged()
        
    

IntAdapter

class IntAdapter : ListAdapter<Int, IntViewHolder>(comparator) 

    companion object 
        private val comparator = object : DiffUtil.ItemCallback<Int>() 
            override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean 
                return oldItem == newItem
            

            override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean 
                return oldItem == newItem
            
        
    

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IntViewHolder 
        return IntViewHolder(
            IntLayoutBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    

    override fun onBindViewHolder(holder: IntViewHolder, position: Int) 
        holder.bindItem(getItem(position))
    


IntViewHolder

class IntViewHolder(val binding: IntLayoutBinding) : RecyclerView.ViewHolder(binding.root) 

    fun bindItem(item: Int?) 
        binding.intNumber.text = item.toString()
    


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_
    android:layout_
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:layout_
        android:layout_
        android:id="@+id/recylerview"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_
        android:layout_
        android:text="add"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

int_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_
    android:layout_>

    <TextView
        android:id="@+id/intNumber"
        android:layout_
        android:layout_
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

【问题讨论】:

【参考方案1】:

您需要确保每次调用 submitList 时传递不同的 List 实例。如果您传递一个 List,对其进行变异,然后再次传递同一个 List 实例,DiffUtil 只会将同一个列表与自身进行比较,因此它将假定列表中没有任何变化并且不会更新任何内容。它对您第一次提交时 List 包含的内容没有某种记忆。

为了进一步概括,您根本不能将可变列表与 ListAdapter 一起使用。 ListAdapter 假定您传递给submitList 的列表没有改变。如果你对其进行变异,可能会出现意想不到的错误。

在您的代码中解决此问题的两种方法。

    每次传递列表时创建一个只读副本:

    intAdapter.submitList(list.toList())

    根本不要使用 MutableList。每次修改列表中应包含的内容时创建一个新列表。这是更简单、更不容易出错的解决方案。

class MainActivity : AppCompatActivity() 

    private var list = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
    private lateinit var binding: ActivityMainBinding
    var i = 0

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        Log.e("List", " $list")
        val intAdapter = IntAdapter()
        binding.recylerview.adapter = intAdapter
        intAdapter.submitList(list)
        binding.button.setOnClickListener 
            list += ++i // create a new list from old list contents plus a new item
            intAdapter.submitList(list)
        
    

旁注:当您将varMutable____ 结合使用时,这应该是一个危险信号。您需要两种不同的方式来改变某些东西应该很少见。这很容易出错。

【讨论】:

你能看看这个新的issue【参考方案2】:

您需要提交一个新的List,目前您正在提交相同的List,该List已被修改。

在您的点击监听器中尝试类似:

binding.button.setOnClickListener 
    val current = intAdapter.currentList
    val update = current + (current.size + 1) // returns a new list with added value
    intAdapter.submitList(update)

这个逻辑的例子:

【讨论】:

以上是关于当视图更新 Android Kotlin 时,DiffUtil 不与 ListAdpater 一起使用的主要内容,如果未能解决你的问题,请参考以下文章

当用户滚动时如何显示或隐藏日期文本视图就像whatsapp聊天android Kotlin

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

在 Android 视图模型中的内部网络更改回调时 LiveData 未触发 - Kotlin

添加新文档时,kotlin firestore 未更新我的 android 应用程序

软输入键盘将我的静态底部视图向上移动 Android Kotlin

当用户切换到 Android Studio Kotlin 中的其他页面时,如何停止播放音乐?