[英]Android Recycleview Selection stops working correctly after navigation between fragments
I implemented SelectionTracker for RecycleView and it works fine until I navigate to another fragment and press the back button.我为 RecycleView 实现了 SelectionTracker,它工作正常,直到我导航到另一个片段并按下后退按钮。 After navigation, it stops working correctly and after selection I can't deselect item anymore.
导航后,它停止正常工作,选择后我无法再取消选择项目。
I created a sample project on github and I can reproduce bug there: https://github.com/alborozd/RecycleViewSelectionProblem我在 github 上创建了一个示例项目,我可以在那里重现错误: https://github.com/alborozd/RecycleViewSelectionProblem
Here is my code from that sample project:这是该示例项目中的代码:
Adapter and ViewHolder:适配器和 ViewHolder:
class MyListAdapter()
: ListAdapter<MyModel, MyListAdapter.MyItemViewHolder>(DiffCallback()) {
init {
setHasStableIds(true)
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
private var tracker: SelectionTracker<String>? = null
fun setTracker(tracker: SelectionTracker<String>?) {
this.tracker = tracker
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyItemViewHolder {
return MyItemViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false),
this
)
}
override fun onBindViewHolder(holder: MyItemViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item, tracker!!.isSelected(item.id))
}
class MyItemViewHolder(itemView: View, private val adapter: MyListAdapter) : RecyclerView.ViewHolder(itemView) {
private var text: TextView? = null
private var container: View? = null
init {
text = itemView.findViewById(R.id.text)
container = itemView.findViewById(R.id.itemContainer)
}
fun bind(item: MyModel, selected: Boolean) {
text?.text = item.name
if (selected) {
val theme = itemView.context!!.theme
container?.setBackgroundColor(
itemView.context!!.resources.getColor(
android.R.color.darker_gray,
theme
)
)
} else {
container?.setBackgroundColor(0)
}
}
fun getItemDetails(): ItemDetailsLookup.ItemDetails<String> =
object : ItemDetailsLookup.ItemDetails<String>() {
override fun getPosition(): Int = adapterPosition
override fun getSelectionKey(): String? = adapter.getItem(adapterPosition).id
override fun inSelectionHotspot(e: MotionEvent): Boolean {
return true
}
}
}
class DiffCallback : DiffUtil.ItemCallback<MyModel>() {
override fun areItemsTheSame(oldItem: MyModel, newItem: MyModel): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: MyModel, newItem: MyModel): Boolean {
return oldItem == newItem
}
}
}
ItemIdKeyProvider: ItemIdKeyProvider:
class ItemIdKeyProvider(
private val adapter: MyListAdapter
) : ItemKeyProvider<String>(SCOPE_MAPPED) {
override fun getKey(position: Int): String? {
return adapter.currentList[position].id
}
override fun getPosition(key: String): Int {
return adapter.currentList.indexOfFirst { c -> c.id == key }
}
}
ItemLookup:项目查找:
class ItemLookup(private val rv: RecyclerView) : ItemDetailsLookup<String>() {
override fun getItemDetails(event: MotionEvent)
: ItemDetails<String>? {
val view = rv.findChildViewUnder(event.x, event.y)
if (view != null) {
return (rv.getChildViewHolder(view) as MyListAdapter.MyItemViewHolder)
.getItemDetails()
}
return null
}
}
And here is how I initialize all of this in my fragment:这是我在片段中初始化所有这些的方式:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
viewModel = createViewModel()
binding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false)
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.initViewModel()
viewModel.items.observe(this, Observer { items ->
val adapter = MyListAdapter()
adapter.submitList(items)
binding.recycleView.adapter = adapter
trackSelectedItems(adapter, binding.recycleView)
adapter.notifyDataSetChanged()
})
binding.btnGoToNextFragment.setOnClickListener {
val action = MainFragmentDirections.actionMainFragmentToOtherFragment()
findNavController().navigate(action)
}
return binding.root
}
private fun trackSelectedItems(
adapter: MyListAdapter,
recyclerView: RecyclerView
) {
tracker = SelectionTracker.Builder<String>(
"selectionTracker",
recyclerView,
ItemIdKeyProvider(adapter),
ItemLookup(recyclerView),
StorageStrategy.createStringStorage()
).withSelectionPredicate(SelectionPredicates.createSelectAnything())
.build()
adapter.setTracker(tracker)
tracker.addObserver(object : SelectionTracker.SelectionObserver<String>() {
override fun onSelectionChanged() {
super.onSelectionChanged()
}
})
}
Steps to reproduce:重现步骤:
Don't initialize adapter
inside live data's observer.不要在实时数据的观察者中初始化
adapter
。 Because Live Data
might be observed n number of times, so if you initialize adapter inside that, adapter will be initialized many times.因为
Live Data
可能会被观察 n 次,所以如果你在其中初始化适配器,适配器将被初始化多次。
To resolve issue use below code要解决问题,请使用以下代码
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
viewModel = createViewModel()
binding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false)
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.initViewModel()
val adapter = MyListAdapter()
binding.recycleView.adapter = adapter
trackSelectedItems(adapter, binding.recycleView)
//adapter.notifyDataSetChanged()
viewModel.items.observe(this, Observer { items ->
adapter.submitList(items)
})
binding.btnGoToNextFragment.setOnClickListener {
val action = MainFragmentDirections.actionMainFragmentToOtherFragment()
findNavController().navigate(action)
}
return binding.root
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.