RecyclerView 不会使用 submitList() 和 LiveData 自动刷新

RecyclerView not refreshed automatically with submitList() and LiveData

情况是这样的:

我在 Fragment 中有一个 RecyclerView 使用 DataBinding。它的适配器是 ListAdapter.

class MyFragment : Fragment() {
    override fun onCreateView(...) {
        val binding = // inflate layout with DataBindingUtil

        val myListAdapter = MyListAdapter()
        binding.recyclerView.adapter = myListAdapter

        val myViewModel: MyViewModel by activityViewModels()
        myViewModel.myLiveData.observe(viewLifecycleOwner, Observer {
            println("before submit: ${myListAdapter.currentList}")
            myListAdapter.submitList(it)
            println("after submit: ${myListAdapter.currentList}")
        })

        return binding.root
    }
}

要显示的数据由 List<Double> 表示。设置后,列表用于初始化 MutableLiveData,如下所示:

class MyViewModel : ViewModel() {
    val data = listOf<Double>(/* init with some values */)
    val myLiveData = MutableLiveData(data)

    fun onButtonClicked() {
        update()
        println(myLiveData.value) // LiveData is correctly updated according to data
    }
}

Note: the update() method changes the list's values like: data[0] = someValue. It does not use clear() nor addAll() to update it.

至此,没有什么特别的。创建片段时,RecyclerView 显示初始列表。在 Logcat:

I/System.out: before submit: []
I/System.out: onCurrentListChanged: [] -> [4.07, 6.29, 13.85, 17.92, 21.42, 23.47, 1.85]
I/System.out: after submit: [4.07, 6.29, 13.85, 17.92, 21.42, 23.47, 1.85]

现在,当我单击按钮时,UI 不会刷新,因为我没有分配新列表,而只更新了当前列表。此外,如果我导航到另一个片段然后返回,RecyclerView 正在显示更新后的值。

所以我在onButtonClicked()末尾添加了以下内容:

myLiveData.value = myLiveData.value

但这不起作用,点击按钮后 UI 仍然没有自动更新。此外,Logcat 仅打印 'before submit' 和 'after submit',具有相同的 ListAdapter.currentList 值:

I/System.out: before submit: [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85]
I/System.out: after submit: [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85]

对我来说,印刷品应该是这样的:

I/System.out: before submit: [4.07, 6.29, 13.85, 17.92, 21.42, 23.47, 1.85]
I/System.out: onCurrentListChanged: [4.07, 6.29, 13.85, 17.92, 21.42, 23.47, 1.85] -> [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85]
I/System.out: after submit: [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85]

似乎 ListAdapter.currentList 在调用 submitList() 之前就已经有了更新值。我想这就是 RecyclerView 没有刷新的原因,因为提交的列表与当前列表相同。
我还在 onButtonClicked() 末尾尝试了以下但没有成功:

//myLiveData.value = myLiveData.value
myLiveData.value = emptyList()
myLiveData.value = data

还有这个……

//myLiveData.value = myLiveData.value
myLiveData.value = emptyList()
//myLiveData.value = data

...给我:

I/System.out: before submit: [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85]
I/System.out: after submit: [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85]
I/System.out: onCurrentListChanged: [4.88, 6.77, 13.85, 17.76, 20.93, 22.69, 1.85] -> []

现在调用 onCurrentListChanged(),但在 2 次打印的末尾而不是在中间。
此外,ListAdapter.currentList 似乎包含 'after submit' 打印中的值,但在 onCurrentListChanged() 打印中实际上是空的。令人困惑...

我发现使 RecyclerView 刷新的唯一方法是调用 notifyDataSetChanged()。但是,使用 ListAdapter 及其 submitList() 方法没有任何好处。

作为一个新手,我可能做错了,但我不知道是什么。
问题仅在于 UI 不刷新。我可以在 Locgcat 中看到数据正在正确更新。

提前致谢

编辑

class MyListAdapter() : ListAdapter<Double, MyViewHolder>(MyDiffCallBack()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
        MyViewHolder.from(parent, itemCount)

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) =
        holder.bind(getItem(position))

    override fun onCurrentListChanged(previousList: MutableList<Double>, currentList: MutableList<Double>) {
        super.onCurrentListChanged(previousList, currentList)
        println("onCurrentListChanged: $previousList -> $currentList")
    }
}

class MyViewHolder private constructor(private val binding: MyItemViewBinding) : RecyclerView.ViewHolder(binding.root) {

    fun bind(data: Double) {
        binding.dataTxt.text = data.toString()
    }

    companion object {
        fun from(parent: ViewGroup, itemCount: Int): MyViewHolder {
            val binding = // inflate layout
            binding.root.layoutParams.height = parent.height / itemCount // set the height proportionally
            return MyViewHolder(binding)
        }
    }
}

class MyDiffCallBack : DiffUtil.ItemCallback<Double>() {
    override fun areItemsTheSame(oldItem: Double, newItem: Double): Boolean = oldItem == newItem

    override fun areContentsTheSame(oldItem: Double, newItem: Double): Boolean = oldItem == newItem

}

浅拷贝和深拷贝的区别问题
如本 article 关于 ListAdapter 所述:

Good to know is that the ListAdapter keeps a reference towards the provided list in submitList(). Always make sure to provide an other list with other item instances. If the references to the items are the same, DiffUtil compares the same items which are always equal, and the RecyclerView will not be updated.