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

RecyclerView条目重复问题求助:含RadioGroup与EditText的列表滚动时数据错乱

RecyclerView条目状态错乱问题分析与解决方案

我来帮你搞定这个RecyclerView滚动时EditText和RadioButton状态混乱的问题,这是视图复用机制下很容易踩的坑,咱们一步步拆解问题,然后给出修复方案。

核心问题原因

你的代码里主要有这几个导致状态错乱的点:

  1. TextWatcher重复绑定+未正确初始化EditText文本:每次bind方法都会新建一个TextWatcher,而且没有先把Service里保存的serviceSolution赋值给EditText,同时holder.watcher没有被正确赋值,导致removeTextChangedListener根本没起作用,旧的Watcher还会留在EditText上,滚动时多个Watcher同时生效,造成文本混乱。
  2. RadioGroup监听重复绑定+状态设置逻辑疏漏:每次bind都给RadioGroup设置新的点击监听,会导致同一个RadioGroup绑定多个监听,触发时多次回调;另外设置RadioButton选中状态时,没有考虑视图复用后残留的旧状态,也可能触发不必要的监听。
  3. 低效的数据更新方式:点击RadioButton后调用notifyDataSetChanged()会刷新整个列表,不仅效率低,还可能导致不必要的视图重绘,加重状态错乱的问题。

修复后的完整代码

1. 修复Adapter类

class ServicesCheckoutAdapter(var context: Context, var servicesList: MutableList<Service>) : RecyclerView.Adapter<ServicesCheckoutAdapter.ViewHolder>() {
    private var onRadioGroupClickListener: OnRadioGroupClickListener? = null
    private var onTextChangedListener: OnTextChangedListener? = null
    private var onServiceClickListener: OnServiceClickListener? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(context).inflate(R.layout.services_list_item, parent, false)
        val viewHolder = ViewHolder(view)
        
        // 把条目点击监听移到这里,避免重复绑定
        view.setOnClickListener {
            val position = viewHolder.adapterPosition
            if (position != RecyclerView.NO_POSITION && onServiceClickListener != null) {
                onServiceClickListener?.onServiceClick(view, servicesList[position].id, position)
            }
        }

        // RadioGroup的选择监听也移到这里初始化
        viewHolder.radioGroup.setOnCheckedChangeListener { group, checkedId ->
            val position = viewHolder.adapterPosition
            if (position != RecyclerView.NO_POSITION && onRadioGroupClickListener != null) {
                onRadioGroupClickListener?.onRadioGroupClick(checkedId, servicesList[position].id, position)
            }
        }

        return viewHolder
    }

    // 重写getItemId,确保每个条目有唯一ID(依赖Service的id唯一性)
    override fun getItemId(position: Int): Long {
        return servicesList[position].id.toLong()
    }

    interface OnServiceClickListener {
        fun onServiceClick(view: View, serviceId: Int, position: Int)
    }

    fun setOnServiceClickListener(listener: OnServiceClickListener) {
        onServiceClickListener = listener
    }

    interface OnRadioGroupClickListener {
        fun onRadioGroupClick(buttonId: Int, serviceId: Int, position: Int)
    }

    fun setOnRadioButtonClickListener(listener: OnRadioGroupClickListener) {
        onRadioGroupClickListener = listener
    }

    interface OnTextChangedListener{
        fun onTextChanged(position: Int, text: String)
    }

    fun setOnTextChangedListener(listener: OnTextChangedListener){
        onTextChangedListener = listener
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val service = servicesList[position]
        holder.bind(context, service, onTextChangedListener)
    }

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

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val radioGroup = itemView.findViewById<RadioGroup>(R.id.radioGroupService)
        val edtSolution = itemView.findViewById<EditText>(R.id.editTextCheckoutDesc)
        private val serviceTitle = itemView.findViewById<TextView>(R.id.serviceTitle)
        // 把TextWatcher存在ViewHolder里,避免重复创建
        private val textWatcher = object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                val position = adapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    onTextChangedListener?.onTextChanged(position, s.toString())
                }
            }

            override fun afterTextChanged(s: Editable?) {}
        }

        private var onTextChangedListener: OnTextChangedListener? = null

        fun bind(context: Context, service: Service, textChangedListener: OnTextChangedListener?) {
            this.onTextChangedListener = textChangedListener
            
            // 设置服务标题
            serviceTitle.text = context.resources.getString(R.string.service_title_comma, service.name)
            
            // 先移除旧的TextWatcher,再设置文本,最后添加新的Watcher,避免数据错乱
            edtSolution.removeTextChangedListener(textWatcher)
            edtSolution.setText(service.serviceSolution)
            edtSolution.addTextChangedListener(textWatcher)
            
            // 设置RadioGroup选中状态,根据Service的serviceSolved字段判断
            val targetRadioId = if (service.serviceSolved) R.id.radioBtnYes else R.id.radioBtnNo
            radioGroup.check(targetRadioId)
        }
    }
}

2. 修复Activity中的Adapter初始化与回调

// 确保serviceList是可变列表,如果原列表是不可变的,转成MutableList
serviceList = occurrence.services.toMutableList()
servicesAdapter = ServicesCheckoutAdapter(this, serviceList)
recyclerViewServices.adapter = servicesAdapter
recyclerViewServices.layoutManager = LinearLayoutManager(this)
// 启用稳定ID,帮助RecyclerView更准确地复用视图
servicesAdapter.setHasStableIds(true)

servicesAdapter.setOnRadioButtonClickListener(object : ServicesCheckoutAdapter.OnRadioGroupClickListener {
    override fun onRadioGroupClick(buttonId: Int, serviceId: Int, position: Int) {
        serviceList[position].serviceSolved = buttonId == R.id.radioBtnYes
        // 只刷新当前修改的条目,替代全量刷新
        servicesAdapter.notifyItemChanged(position)
    }
})

servicesAdapter.setOnTextChangedListener(object : ServicesCheckoutAdapter.OnTextChangedListener{
    override fun onTextChanged(position: Int, text: String) {
        serviceList[position].serviceSolution = text
        // 实时输入时数据已同步,无需额外刷新视图
    }
})

关键修复点说明

  • 监听只绑定一次:把View和RadioGroup的点击监听移到onCreateViewHolder里,避免每次bind都重复绑定,导致多次回调。
  • TextWatcher复用与正确解绑:把TextWatcher存在ViewHolder中,每次bind时先移除再设置文本,确保EditText的内容和Service的数据一致,同时避免多个Watcher干扰。
  • 高效更新视图:用notifyItemChanged(position)代替notifyDataSetChanged(),只刷新当前修改的条目,提升性能同时减少状态错乱的可能。
  • 启用稳定ID:调用setHasStableIds(true)并重写getItemId,让RecyclerView能更准确地跟踪每个条目,减少不必要的视图复用错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 21:47:30