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

Android中RecyclerView点击切换背景及默认选中项失效问题

RecyclerView默认选中项导致点击切换背景失效的修复方案

问题描述

在Android应用中使用RecyclerView,需求为:

  • 点击条目时切换背景
  • 初始默认选中指定项
  • 用户点击其他项时,切换选中背景并取消原默认项的选中状态

添加selectedItem = 1设置默认选中项后,点击条目无法切换背景;移除该行代码后功能正常,需解决此问题。

原代码:

class StateAdapter constructor() : RecyclerView.Adapter<StateAdapter.ViewHolder>() {

    private lateinit var binding: ItemSimpleTxtBinding
    private lateinit var context: Context
    private var moviesList = emptyList<StateTypes>()
    private var selectedItem = -1

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        binding = ItemSimpleTxtBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        context = parent.context
        return ViewHolder()
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        //getItem from PagingDataAdapter
        holder.bind(moviesList[position], position)
        //Not duplicate items
        holder.setIsRecyclable(false)
    }

    override fun getItemCount() = moviesList.size

    inner class ViewHolder : RecyclerView.ViewHolder(binding.root) {

        @SuppressLint("SetTextI18n")
        fun bind(item: StateTypes, position: Int) {
            binding.apply {
                itemTxt.text = item.name
                //Click
                binding.itemCard.setOnClickListener {
                    selectedItem = adapterPosition
                    notifyDataSetChanged()
                    onItemClickListener?.let {
                        it(position)
                    }
                }
                //Change color
                if (selectedItem == adapterPosition) {
                    binding.itemCard.setCardBackgroundColor(ContextCompat.getColor(context, R.color.meatBrown))
                } else {
                    binding.itemCard.setCardBackgroundColor(ContextCompat.getColor(context, R.color.white))
                }
                //Default
                selectedItem = 1
            }
        }
    }

    private var onItemClickListener: ((Int) -> Unit)? = null

    fun setOnItemClickListener(listener: (Int) -> Unit) {
        onItemClickListener = listener
    }

    fun setData(data: List<StateTypes>) {
        val moviesDiffUtil = NotesDiffUtils(moviesList, data)
        val diffUtils = DiffUtil.calculateDiff(moviesDiffUtil)
        moviesList = data
        diffUtils.dispatchUpdatesTo(this)
    }

    class NotesDiffUtils(private val oldItem: List<StateTypes>, private val newItem: List<StateTypes>) :
        DiffUtil.Callback() {

        override fun getOldListSize(): Int {
            return oldItem.size
        }

        override fun getNewListSize(): Int {
            return newItem.size
        }

        override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return oldItem[oldItemPosition] === newItem[newItemPosition]
        }

        override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return oldItem[oldItemPosition] === newItem[newItemPosition]
        }
    }
}

问题原因

你将selectedItem = 1写在了bind方法的末尾,这会导致每次RecyclerView绑定ViewHolder时,selectedItem都会被强制重置为1。也就是说,点击条目后刚把selectedItem改为点击的位置,下一次bind执行时又被改回1,永远无法切换选中状态。

修复方案

1. 正确设置默认选中项

将默认选中的赋值从bind方法中移除,改为在Adapter初始化或数据加载完成后设置(避免数据为空时数组越界)。

2. 优化刷新逻辑

替换notifyDataSetChanged()为局部刷新,只刷新旧选中项和新选中项,提升性能。

修改后的完整代码:

class StateAdapter constructor() : RecyclerView.Adapter<StateAdapter.ViewHolder>() {

    private lateinit var binding: ItemSimpleTxtBinding
    private lateinit var context: Context
    private var moviesList = emptyList<StateTypes>()
    // 初始化时设置默认选中项,或者在setData中设置更安全
    private var selectedItem = 1

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        binding = ItemSimpleTxtBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        context = parent.context
        return ViewHolder()
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(moviesList[position], position)
        holder.setIsRecyclable(false)
    }

    override fun getItemCount() = moviesList.size

    inner class ViewHolder : RecyclerView.ViewHolder(binding.root) {

        @SuppressLint("SetTextI18n")
        fun bind(item: StateTypes, position: Int) {
            binding.apply {
                itemTxt.text = item.name
                //Click
                binding.itemCard.setOnClickListener {
                    val oldSelected = selectedItem
                    selectedItem = adapterPosition
                    // 只刷新旧选中项和新选中项,提升性能
                    notifyItemChanged(oldSelected)
                    notifyItemChanged(selectedItem)
                    onItemClickListener?.let {
                        it(position)
                    }
                }
                //Change color
                if (selectedItem == adapterPosition) {
                    binding.itemCard.setCardBackgroundColor(ContextCompat.getColor(context, R.color.meatBrown))
                } else {
                    binding.itemCard.setCardBackgroundColor(ContextCompat.getColor(context, R.color.white))
                }
                // 移除这里的selectedItem = 1
            }
        }
    }

    private var onItemClickListener: ((Int) -> Unit)? = null

    fun setOnItemClickListener(listener: (Int) -> Unit) {
        onItemClickListener = listener
    }

    fun setData(data: List<StateTypes>) {
        val moviesDiffUtil = NotesDiffUtils(moviesList, data)
        val diffUtils = DiffUtil.calculateDiff(moviesDiffUtil)
        moviesList = data
        diffUtils.dispatchUpdatesTo(this)
        // 如果需要确保数据加载后再设置默认选中(比如数据可能为空),可以在这里设置
        // if (moviesList.size > 1) selectedItem = 1
    }

    class NotesDiffUtils(private val oldItem: List<StateTypes>, private val newItem: List<StateTypes>) :
        DiffUtil.Callback() {

        override fun getOldListSize(): Int {
            return oldItem.size
        }

        override fun getNewListSize(): Int {
            return newItem.size
        }

        override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return oldItem[oldItemPosition] === newItem[newItemPosition]
        }

        override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            return oldItem[oldItemPosition] === newItem[newItemPosition]
        }
    }
}

额外说明

  • 如果你的数据是异步加载的,建议在setData方法中判断数据长度后再设置selectedItem = 1,避免数组越界。
  • 若无特殊需求,建议移除holder.setIsRecyclable(false),RecyclerView的复用机制能提升性能,强制关闭复用可能导致内存问题。

内容的提问来源于stack exchange,提问作者Dr.KeyOk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 19:09:23