Android 在片段之间导航后,Recycleview Selection 停止正常工作
Android Recycleview Selection stops working correctly after navigation between fragments
我为 RecycleView 实现了 SelectionTracker,它工作正常,直到我导航到另一个片段并按下后退按钮。导航后,它停止正常工作,选择后我无法再取消选择项目。
我在 github 上创建了一个示例项目,我可以在那里重现错误:
https://github.com/alborozd/RecycleViewSelectionProblem
这是我在该示例项目中的代码:
适配器和 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:
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 }
}
}
项目查找:
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
}
}
下面是我如何在我的片段中初始化所有这些:
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()
}
})
}
重现步骤:
- 用回收视图打开第一个片段,尝试 select/deselect 项。一切正常
- 转到另一个片段,然后按返回按钮
- 再次尝试 select/deselect 个项目,您会发现取消选择不再有效
不要在实时数据的观察器中初始化 adapter
。因为Live Data
可能会被观察n次,所以如果在里面初始化adapter,adapter会被初始化很多次。
要解决问题,请使用以下代码
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
}
我为 RecycleView 实现了 SelectionTracker,它工作正常,直到我导航到另一个片段并按下后退按钮。导航后,它停止正常工作,选择后我无法再取消选择项目。
我在 github 上创建了一个示例项目,我可以在那里重现错误: https://github.com/alborozd/RecycleViewSelectionProblem
这是我在该示例项目中的代码:
适配器和 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:
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 }
}
}
项目查找:
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
}
}
下面是我如何在我的片段中初始化所有这些:
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()
}
})
}
重现步骤:
- 用回收视图打开第一个片段,尝试 select/deselect 项。一切正常
- 转到另一个片段,然后按返回按钮
- 再次尝试 select/deselect 个项目,您会发现取消选择不再有效
不要在实时数据的观察器中初始化 adapter
。因为Live Data
可能会被观察n次,所以如果在里面初始化adapter,adapter会被初始化很多次。
要解决问题,请使用以下代码
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
}