android ListAdapter 使用 MVVM 选择状态
android ListAdapter selected state using MVVM
我找不到使用 MVVM 架构在 ListAdapter 中选择状态的好方法。我还使用由@Doug Stevenson
为 firestore 实现的存储库 LiveData
我试图避免 notifyDataSetChanged()
因为在 UI
处造成了明显的故障
在几天的尝试中,这是我最后一次尝试,但没有成功,因为 oldList
被保存为参考,所以 DiffUtil.ItemCallback
没有检测到任何变化。看来我需要对列表进行深度克隆,但我认为必须有更好的性能解决方案。
MainViewModel
class MainViewModel @ViewModelInject constructor(private val glistRepo: GlistRepository): ViewModel() {
private val selected = MutableLiveData<HashMap<String,Glist>>()
init {
selected.value = HashMap()
}
fun getSelected(): LiveData<HashMap<String,Glist>> {
return selected
}
fun onItemClicked(glist: Glist) {
selected.value?.set(glist.id, glist)
selected.value = selected.value //inform observers
}
....
}
主要片段
viewModel.getSelected().observe(viewLifecycleOwner, Observer {
adapter.setSelected(it.keys.toList())
})
存储库通用
对于选定的状态,我添加了一个 sel
对象,但由于我不想将它添加到数据库中,所以我添加到包装器接口中
interface QueryItem<T> {
val item: T
val id: String
var sel: Boolean //The selected state
}
主适配器
class MainAdapter(private val clickListener: MainAdapterListener):
ListAdapter<QueryItem<Glist>, MainAdapter.ViewHolder>(asyncDifferConfig) {
private val selected = HashSet<String>()
private var oldList: List<QueryItem<Glist>>? = ArrayList()
fun setSelected(list: List<String>) {
selected.clear()
selected.addAll(list)
//Instead of using notifyDataSetChanged() i am trying to trigger the diffUtil. Not working!
submitList(oldList)
}
override fun submitList(list: List<QueryItem<Glist>>?) {
if (selected.size != 0 && oldList != null && oldList!!.isNotEmpty()) {
var id: String
for (i in oldList!!.indices) {
oldList!![i].sel = false
id = oldList!![i].id
selected.forEach { key ->
if (id == key) {
oldList!![i].sel = true
}
}
}
}
oldList = list
super.submitList(oldList)
}
QueryItemDiffCallback
open class QueryItemDiffCallback<T> : DiffUtil.ItemCallback<QueryItem<T>>() {
override fun areItemsTheSame(oldItem: QueryItem<T>, newItem: QueryItem<T>): Boolean {
return (oldItem.id == newItem.id && oldItem.sel == newItem.sel)
}
@SuppressLint("DiffUtilEquals") // equals() is OK for data classes
override fun areContentsTheSame(oldItem: QueryItem<T>, newItem: QueryItem<T>): Boolean {
return (oldItem.item == newItem.item && oldItem.sel == newItem.sel)
}
}
我不确定这是最好的做法,或者即使它与 MVVM 匹配,但考虑到,这是我能想到的最好的做法。如果您有其他想法,请告诉我,不要着急降级
在 viewmodel
中,我保存了选定的列表,并在 MainAdapter
中更改了最后一个项目 ID,以便于 notifyItemChanged()
。在 MainAdapter 中,我使用 Data Binding 变量在 onBindViewHolder
中注入选定状态。
MainViewModel
class MainViewModel @ViewModelInject constructor(private val glistRepo: GlistRepository): ViewModel() {
private val selected = MutableLiveData<HashMap<String,Glist>>()
var lastSelectedId = ""
private set
init {
selected.value = HashMap()
}
private fun toggleSelected(glist:Glist) {
lastSelectedId = glist.id
if (selected.value!!.containsKey(glist.id)) {
selected.value?.remove(lastSelectedId)
} else {
selected.value?.set(lastSelectedId, glist)
}
selected.value = selected.value //Notify observers
}
fun getSelected(): LiveData<HashMap<String,Glist>> {
return selected
}
fun onItemClicked(glist: Glist, short: Boolean) {
if (!short || selected.value!!.size != 0) {
toggleSelected(glist)
} else {
selected.value?.clear()
_navigateToItem.value = glist.id
}
}
...
}
主要片段
viewModel.getSelected().observe(viewLifecycleOwner, Observer {
adapter.setSelected(it.keys.toList(),viewModel.lastSelectedId)
})
主适配器
class MainAdapter(private val clickListener: MainAdapterListener):
ListAdapter<QueryItem<Glist>, MainAdapter.ViewHolder>(asyncDifferConfig) {
...
private val selected = HashSet<String>()
fun setSelected(list: List<String>, lastChangedId: String) {
selected.clear()
selected.addAll(list)
for (i in 0 until currentList.size) {
if (currentList[i].id == lastChangedId) {
notifyItemChanged(i)
return
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item,clickListener,selected.contains(item.id))
}
...
class ViewHolder private constructor(val binding: ListMainBinding): RecyclerView.ViewHolder(binding.root){
fun bind(item: QueryItem<Glist>, clickListener: MainAdapterListener, isSelected: Boolean) {
binding.glist = item.item
binding.clickListener = clickListener
binding.isSelected = isSelected
binding.executePendingBindings()
}
...
}
}
我找不到使用 MVVM 架构在 ListAdapter 中选择状态的好方法。我还使用由@Doug Stevenson
为 firestore 实现的存储库 LiveData我试图避免 notifyDataSetChanged()
因为在 UI
在几天的尝试中,这是我最后一次尝试,但没有成功,因为 oldList
被保存为参考,所以 DiffUtil.ItemCallback
没有检测到任何变化。看来我需要对列表进行深度克隆,但我认为必须有更好的性能解决方案。
MainViewModel
class MainViewModel @ViewModelInject constructor(private val glistRepo: GlistRepository): ViewModel() {
private val selected = MutableLiveData<HashMap<String,Glist>>()
init {
selected.value = HashMap()
}
fun getSelected(): LiveData<HashMap<String,Glist>> {
return selected
}
fun onItemClicked(glist: Glist) {
selected.value?.set(glist.id, glist)
selected.value = selected.value //inform observers
}
....
}
主要片段
viewModel.getSelected().observe(viewLifecycleOwner, Observer {
adapter.setSelected(it.keys.toList())
})
存储库通用
对于选定的状态,我添加了一个 sel
对象,但由于我不想将它添加到数据库中,所以我添加到包装器接口中
interface QueryItem<T> {
val item: T
val id: String
var sel: Boolean //The selected state
}
主适配器
class MainAdapter(private val clickListener: MainAdapterListener):
ListAdapter<QueryItem<Glist>, MainAdapter.ViewHolder>(asyncDifferConfig) {
private val selected = HashSet<String>()
private var oldList: List<QueryItem<Glist>>? = ArrayList()
fun setSelected(list: List<String>) {
selected.clear()
selected.addAll(list)
//Instead of using notifyDataSetChanged() i am trying to trigger the diffUtil. Not working!
submitList(oldList)
}
override fun submitList(list: List<QueryItem<Glist>>?) {
if (selected.size != 0 && oldList != null && oldList!!.isNotEmpty()) {
var id: String
for (i in oldList!!.indices) {
oldList!![i].sel = false
id = oldList!![i].id
selected.forEach { key ->
if (id == key) {
oldList!![i].sel = true
}
}
}
}
oldList = list
super.submitList(oldList)
}
QueryItemDiffCallback
open class QueryItemDiffCallback<T> : DiffUtil.ItemCallback<QueryItem<T>>() {
override fun areItemsTheSame(oldItem: QueryItem<T>, newItem: QueryItem<T>): Boolean {
return (oldItem.id == newItem.id && oldItem.sel == newItem.sel)
}
@SuppressLint("DiffUtilEquals") // equals() is OK for data classes
override fun areContentsTheSame(oldItem: QueryItem<T>, newItem: QueryItem<T>): Boolean {
return (oldItem.item == newItem.item && oldItem.sel == newItem.sel)
}
}
我不确定这是最好的做法,或者即使它与 MVVM 匹配,但考虑到,这是我能想到的最好的做法。如果您有其他想法,请告诉我,不要着急降级
在 viewmodel
中,我保存了选定的列表,并在 MainAdapter
中更改了最后一个项目 ID,以便于 notifyItemChanged()
。在 MainAdapter 中,我使用 Data Binding 变量在 onBindViewHolder
中注入选定状态。
MainViewModel
class MainViewModel @ViewModelInject constructor(private val glistRepo: GlistRepository): ViewModel() {
private val selected = MutableLiveData<HashMap<String,Glist>>()
var lastSelectedId = ""
private set
init {
selected.value = HashMap()
}
private fun toggleSelected(glist:Glist) {
lastSelectedId = glist.id
if (selected.value!!.containsKey(glist.id)) {
selected.value?.remove(lastSelectedId)
} else {
selected.value?.set(lastSelectedId, glist)
}
selected.value = selected.value //Notify observers
}
fun getSelected(): LiveData<HashMap<String,Glist>> {
return selected
}
fun onItemClicked(glist: Glist, short: Boolean) {
if (!short || selected.value!!.size != 0) {
toggleSelected(glist)
} else {
selected.value?.clear()
_navigateToItem.value = glist.id
}
}
...
}
主要片段
viewModel.getSelected().observe(viewLifecycleOwner, Observer {
adapter.setSelected(it.keys.toList(),viewModel.lastSelectedId)
})
主适配器
class MainAdapter(private val clickListener: MainAdapterListener):
ListAdapter<QueryItem<Glist>, MainAdapter.ViewHolder>(asyncDifferConfig) {
...
private val selected = HashSet<String>()
fun setSelected(list: List<String>, lastChangedId: String) {
selected.clear()
selected.addAll(list)
for (i in 0 until currentList.size) {
if (currentList[i].id == lastChangedId) {
notifyItemChanged(i)
return
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item,clickListener,selected.contains(item.id))
}
...
class ViewHolder private constructor(val binding: ListMainBinding): RecyclerView.ViewHolder(binding.root){
fun bind(item: QueryItem<Glist>, clickListener: MainAdapterListener, isSelected: Boolean) {
binding.glist = item.item
binding.clickListener = clickListener
binding.isSelected = isSelected
binding.executePendingBindings()
}
...
}
}