RecyclerView条目重复问题求助:含RadioGroup与EditText的列表滚动时数据错乱
RecyclerView条目状态错乱问题分析与解决方案
我来帮你搞定这个RecyclerView滚动时EditText和RadioButton状态混乱的问题,这是视图复用机制下很容易踩的坑,咱们一步步拆解问题,然后给出修复方案。
核心问题原因
你的代码里主要有这几个导致状态错乱的点:
- TextWatcher重复绑定+未正确初始化EditText文本:每次
bind方法都会新建一个TextWatcher,而且没有先把Service里保存的serviceSolution赋值给EditText,同时holder.watcher没有被正确赋值,导致removeTextChangedListener根本没起作用,旧的Watcher还会留在EditText上,滚动时多个Watcher同时生效,造成文本混乱。 - RadioGroup监听重复绑定+状态设置逻辑疏漏:每次
bind都给RadioGroup设置新的点击监听,会导致同一个RadioGroup绑定多个监听,触发时多次回调;另外设置RadioButton选中状态时,没有考虑视图复用后残留的旧状态,也可能触发不必要的监听。 - 低效的数据更新方式:点击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
相关产品推荐
相关产品推荐

