【问题标题】:DiffUtil Not working with ListAdpater when view is updating Android Kotlin当视图更新 Android Kotlin 时,DiffUtil 不与 ListAdpater 一起使用
【发布时间】: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_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        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_width="wrap_content"
        android:layout_height="wrap_content"
        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_width="match_parent"
    android:layout_height="wrap_content">

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

</androidx.constraintlayout.widget.ConstraintLayout>

【问题讨论】:

    标签: android kotlin android-recyclerview android-diffutils android-listadapter


    【解决方案1】:

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

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

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

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

      intAdapter.submitList(list.toList())

    2. 根本不要使用 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)
    }
    

    这个逻辑的例子:

    【讨论】:

      猜你喜欢
      • 2021-12-11
      • 2018-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-01
      • 2020-07-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多