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.
- 此外,我覆盖
ListAdapter.onCurrentListChanged()
只是为了在里面放一个println()
。
至此,没有什么特别的。创建片段时,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.
情况是这样的:
我在 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 useclear()
noraddAll()
to update it.
- 此外,我覆盖
ListAdapter.onCurrentListChanged()
只是为了在里面放一个println()
。
至此,没有什么特别的。创建片段时,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 insubmitList()
. 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 theRecyclerView
will not be updated.