RecyclerView 和 notifyDataSetChanged LongClick 不匹配

Posted

技术标签:

【中文标题】RecyclerView 和 notifyDataSetChanged LongClick 不匹配【英文标题】:RecyclerView and notifyDataSetChanged LongClick mismatch 【发布时间】:2021-12-09 21:32:15 【问题描述】:

我的 Recycler Adapter 中的 notifyDataSetChanged() 有一个奇怪的问题。如果我在一个数组中保留 5 个项目,则代码可以正常工作,我可以选中我 LongClick 项目上的复选框,但是当我向数组中添加 5 个或更多项目时,其他复选框会在我的列表中被选中。

当用户 LongClicks 时,我使用布尔值在复选框上的 VISIBLE 和 GONE 之间切换。

这是我的代码:

class RecyclerAdapter(private val listActivity: ListActivity) : RecyclerView.Adapter<RecyclerAdapter.Holder>() 

    lateinit var binding: ActivityListItemRowBinding
    var checkboxesVisibility = false
    val dummyArrayWorks = arrayOf("000", "111", "222", "333", "444")
    val dummyArrayFails = arrayOf("000", "111", "222", "333", "444", "555")

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder 
        binding = ActivityListItemRowBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return Holder(binding)
    

    override fun getItemCount(): Int = dummyArrayFails.size

    @SuppressLint("NotifyDataSetChanged")
    override fun onBindViewHolder(holder: Holder, position: Int) 

        val item = dummyArrayFails[position]
        
        holder.binding.checkbox.visibility = if (checkboxesVisibility) VISIBLE else GONE
        holder.bindItem(item)

        holder.itemView.setOnLongClickListener 
            if (!checkboxesVisibility) 
                checkboxesVisibility = true
                holder.binding.checkbox.isChecked = true
                notifyDataSetChanged()
                true
             else 
                false
            
        
        holder.itemView.setOnClickListener 
            if (!checkboxesVisibility) 
                //Some other unrelated code
             else 
                holder.binding.checkbox.isChecked = !holder.binding.checkbox.isChecked
                notifyDataSetChanged()
            
        
    

    class Holder(internal val binding: ActivityListItemRowBinding) : RecyclerView.ViewHolder(binding.root) 

        var item = String()

        fun bindItem(item: String) 
            this.item = item
            binding.itemPlaceHolder.text = item
        
    

我应该补充一点,当我移除复选框的切换开关并在第一次加载时显示复选框时,点击匹配复选标记没有问题。

有人知道发生了什么吗?我们将不胜感激所有帮助!

【问题讨论】:

如果您需要更多代码,我很乐意添加一些! 【参考方案1】:

问题是您在 ViewHolder 本身中保持选中状态 - 您正在根据点击打开和关闭其复选框,对吗?

RecyclerView 的工作方式是,不是为每个项目都设置一个 ViewHolder(就像 ListView 那样),它只创建少数几个 - 足以用于屏幕上的内容,而更多用于滚动 - 并回收这些,使用它们来显示不同的项目。

这就是onBindViewHolder 的意义所在——当它需要在position 上显示该项目时,它会从它的池中给你一个ViewHolder,并说你去,用它来显示这个项目的详细信息。您可以在此处执行设置文本、更改图像以及设置复选框状态等操作以反映该特定项目。


您所做的是您没有将项目的状态存储在任何地方,您只是在 view holder 上设置复选框。因此,如果您选中它,则恰好显示在该可重用持有者对象中的每个项目都会勾选其框。这就是为什么您会看到它在其他项目上弹出的原因 - 选中状态与项目本身无关,只是由于它们在列表中的位置,它们都碰巧使用了哪个视图持有者。

因此,您需要将它们的选中状态保留在某个地方——它可以像一个与您的项目列表长度相匹配的布尔数组一样简单。然后,您只需在绑定数据(显示它)时设置并从中获取。使用你所拥有的:

// all default to false
val itemChecked = BooleanArray(items.size)

override fun onBindViewHolder(holder: Holder, position: Int) 
    ...
    // when displaying the data, refer to the checked state we're holding
    holder.binding.checkbox.checked = itemChecked[position]
    ...
    holder.itemView.setOnLongClickListener 
        ...
        // when checking the box, update our checked state
        // since we're calling notifyDataSetChanged, the item will be redisplayed
        // and onBindViewHolder will be called again (which sets the checkbox)
        itemChecked[position] = true
        // notifyItemChanged(position) is better here btw, just refreshes this one
        notifyDataSetChanged()
        ...
    

【讨论】:

太棒了!虽然我不同意你关于 notifyItemChanged(position) 的想法。在那种情况下,我可能更喜欢 notifyItemRangeChanged(0, items.size) ,但我想这是一个品味问题 ;-) 我在这个问题上徘徊,但我也(在我的帖子之外)向我的 SQLite 数据库添加了一个 INTEGER 属性以检查已检查的项目。这样干净多了。 notifyItemRangeChanged(0, items.size)notifyDataSetChanged 好,但它仍然告诉适配器每个项目都需要更新。 notifyItemChanged(position) 字面上只是“嘿,这个项目已经改变了,所以更新它”这就是正在发生的事情,它更有效,但它也更能描述你在做什么。长按项目->更新该项目的状态->触发该项目的UI刷新以反映新状态 如果你想在应用程序运行之间保持这种状态,你可能还需要那个 SQL 列,但是是的,你需要单独存储和更新该状态 - 读取和写入数据库太慢了对于 UI 更改,因此您也需要该内存版本。此外,如果您想提高效率,您可能希望在每个视图持有者 once 上设置点击侦听器(通常在视图持有者的 init 块中进行)并给它一个 position财产。然后你将它的position 设置为onBindViewHolder(它当前显示的位置),点击监听器只是参考那个

以上是关于RecyclerView 和 notifyDataSetChanged LongClick 不匹配的主要内容,如果未能解决你的问题,请参考以下文章

RecyclerView和ListView的区别

RecyclerView详解

BaseQuickAdapter和BaseViewHolder使用 RecyclerView点击事件 RecyclerView添加子view点击事件 RecyclerView添加子view点击事

RecyclerView小结

recyclerview和listview的区别

怎么导入recyclerview包