You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RecyclerView中实现单选Checkbox遇选中错误问题求助

RecyclerView单选Checkbox选中错误问题修正

问题说明

需要在RecyclerView中实现单选Checkbox功能,不想调用notifyDataSetChanged,但尝试代码后出现选中状态错误的问题。

原问题代码

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.tps.challenge.R
import com.tps.challenge.domain.Store
import com.tps.challenge.ui.Event

class StoreFeedAdapter(
    private val stores: List<Store>,
    private val itemClickListener: (Event<Store>) -> Unit
): RecyclerView.Adapter<StoreItemViewHolder>() {

    private var prevSelectedPos: Int = 0
    private var currSelectedPos: Int = 0

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StoreItemViewHolder {
        return StoreItemViewHolder(
            LayoutInflater.from(parent.context)
                .inflate(R.layout.item_store, parent, false)
        ).also { viewHolder ->
            viewHolder.storeImage.setOnClickListener {
                val selectedStore = stores[viewHolder.adapterPosition]
                itemClickListener.invoke(
                    Event(selectedStore)
                )
            }
            viewHolder.checkbox.setOnClickListener {
                currSelectedPos = viewHolder.adapterPosition
                notifyItemChanged(prevSelectedPos)
                notifyItemChanged(currSelectedPos)
                prevSelectedPos = currSelectedPos
            }
        }
    }

    override fun onBindViewHolder(holder: StoreItemViewHolder, position: Int) {
        holder.checkbox.isSelected = currSelectedPos == position
        val store = stores[position]

        with(holder.itemView) {
            findViewById<TextView>(R.id.name).text = store.name
            findViewById<TextView>(R.id.description).text = store.desc
            Glide.with(holder.itemView.context)
                .load(store.coverImgUrl)
                .into(holder.storeImage)
        }
    }

    override fun getItemCount(): Int {
        return stores.size
    }
}

class StoreItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val storeImage: ImageView = itemView.findViewById(R.id.iv_store)
    val checkbox: CheckBox = itemView.findViewById(R.id.cb_select)
}

问题分析

  • 初始值错误:prevSelectedPos和currSelectedPos初始都设为0,会默认选中第0项,且未考虑无选中的场景。
  • 状态设置错误:使用isSelected控制CheckBox状态,CheckBox的勾选状态应由isChecked控制,两者属于不同概念。
  • 点击逻辑缺陷:未处理点击已选中项的情况,冗余变量易导致状态混乱。
  • ViewHolder复用问题:滚动时可能因状态绑定错误导致选中状态错乱。

修正后的代码

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.tps.challenge.R
import com.tps.challenge.domain.Store
import com.tps.challenge.ui.Event

class StoreFeedAdapter(
    private val stores: List<Store>,
    private val itemClickListener: (Event<Store>) -> Unit
): RecyclerView.Adapter<StoreItemViewHolder>() {

    // 初始设为-1表示无默认选中项,需默认选中可改为对应位置
    private var selectedPos: Int = -1

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StoreItemViewHolder {
        return StoreItemViewHolder(
            LayoutInflater.from(parent.context)
                .inflate(R.layout.item_store, parent, false)
        ).also { viewHolder ->
            viewHolder.storeImage.setOnClickListener {
                val selectedStore = stores[viewHolder.adapterPosition]
                itemClickListener.invoke(Event(selectedStore))
            }
            viewHolder.checkbox.setOnClickListener {
                val position = viewHolder.adapterPosition
                // 点击已选中项时取消选中
                if (selectedPos == position) {
                    selectedPos = -1
                    notifyItemChanged(position)
                } else {
                    val oldSelectedPos = selectedPos
                    selectedPos = position
                    // 仅刷新状态变化的项,避免全局刷新
                    if (oldSelectedPos != -1) {
                        notifyItemChanged(oldSelectedPos)
                    }
                    notifyItemChanged(selectedPos)
                }
            }
        }
    }

    override fun onBindViewHolder(holder: StoreItemViewHolder, position: Int) {
        // 用isChecked正确绑定CheckBox勾选状态
        holder.checkbox.isChecked = selectedPos == position
        val store = stores[position]

        with(holder.itemView) {
            findViewById<TextView>(R.id.name).text = store.name
            findViewById<TextView>(R.id.description).text = store.desc
            Glide.with(context)
                .load(store.coverImgUrl)
                .into(holder.storeImage)
        }
    }

    override fun getItemCount(): Int = stores.size
}

class StoreItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val storeImage: ImageView = itemView.findViewById(R.id.iv_store)
    val checkbox: CheckBox = itemView.findViewById(R.id.cb_select)
}

修正要点

  • 简化状态管理:用单个selectedPos替代冗余变量,初始值设为-1表示无选中项。
  • 修正状态绑定:将isSelected改为isChecked,匹配CheckBox的勾选状态逻辑。
  • 优化点击逻辑:支持点击已选中项取消选中,仅刷新状态变化的列表项,提升性能。
  • 适配ViewHolder复用:在onBindViewHolder中正确绑定状态,确保滚动时选中状态准确。

内容的提问来源于stack exchange,提问作者HukeLau_DABA

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 07:35:18