简体   繁体   English

Android Recycleview Selection 在片段之间导航后停止正常工作

[英]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:重现步骤:

  1. Open first fragment with recycleview, try to select/deselect items.使用 recycleview 打开第一个片段,尝试选择/取消选择项目。 Everything works fine一切正常
  2. Go to another fragment, then press back button Go 到另一个片段,然后按返回按钮
  3. Try to select/deselect items again and you'll see that deselection doesn't work anymore再次尝试选择/取消选择项目,您会发现取消选择不再起作用

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM