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

调用notifyDataSetChanged时仅RecyclerView最后一项更新的问题

问题与解决方案

问题描述

需要实现一个带选择功能的分段列表,布局包含多个数据动态变化的RecyclerView。每个RecyclerView的列表项带有Checkbox,需求是:当用户选中某一项时,仅该分段的列表项保持启用状态,其他分段的列表项需禁用。但调用notifyDataSetChanged更新RecyclerView时,仅列表的最后一项变为禁用状态,其余项仍为启用状态,调试显示流程正常但界面未匹配预期。

核心问题分析

问题出在Adapter的Binding实例复用上:原Adapter中将LayoutChannelDeviceBinding定义为Adapter的成员变量,所有ViewHolder共享同一个Binding实例。当RecyclerView复用ViewHolder时,前面的项的UI会被最后一次绑定的Binding覆盖,导致只有最后一项能正确更新状态。

解决方案

1. 修正Adapter的ViewHolder绑定逻辑

将LayoutChannelDeviceBinding移至ViewHolder内部,让每个ViewHolder持有独立的Binding实例,避免复用冲突。

2. 优化Checkbox状态更新逻辑

在设置Checkbox状态前先移除监听器,防止状态更新时触发不必要的回调;设置完状态后再重新绑定监听器。

3. 简化MainActivity的数据同步逻辑

直接操作原始的segments列表,无需创建新的alteredList,避免浅拷贝导致的数据同步混乱。


修改后的代码

数据模型(保持不变)

data class SelectionModel(
    var channelType: String,
    var channelName: String,
    var devices: ArrayList<SelectiveDevice>?
) {
    data class SelectiveDevice(
        var deviceName: String,
        var deviceType: String,
        var selected: Boolean = false
    )
}

修改后的FiveChannelDeviceAdapter

class FiveChannelDeviceAdapter(
    private val context: Context,
    private val callback: FiveChannelDeviceCallback
) : RecyclerView.Adapter<FiveChannelDeviceAdapter.FiveChannelDeviceViewHolder>() {

    private var mDeviceLists = ArrayList<SelectionModel.SelectiveDevice>()
    var selectedTypes = ""
    var selectedGroups = ""
    var countCheckeds = 0

    inner class FiveChannelDeviceViewHolder(private val binding: LayoutChannelDeviceBinding) : RecyclerView.ViewHolder(binding.root) {

        fun setData(position: Int) {
            val device = mDeviceLists[position]
            // 先移除监听器,防止状态更新触发回调
            binding.selectDeviceCheck.setOnCheckedChangeListener(null)

            if (countCheckeds > 0) {
                if (device.deviceType == selectedTypes) {
                    binding.selectDeviceCheck.isEnabled = true
                    binding.selectDeviceCheck.isClickable = true
                    binding.selectDeviceCheck.isChecked = device.selected
                    binding.tvDeviceName.text = "Enabled"
                } else {
                    binding.selectDeviceCheck.isEnabled = false
                    binding.selectDeviceCheck.isClickable = false
                    binding.selectDeviceCheck.isChecked = false // 非当前分段项强制取消选中
                    binding.tvDeviceName.text = "Disabled"
                }
            } else {
                binding.selectDeviceCheck.isEnabled = true
                binding.selectDeviceCheck.isClickable = true
                binding.selectDeviceCheck.isChecked = device.selected
                binding.tvDeviceName.text = "Enabled"
            }

            // 重新绑定监听器
            binding.selectDeviceCheck.setOnCheckedChangeListener { _, checked ->
                device.selected = checked
                if (checked) {
                    countCheckeds += 1
                    selectedTypes = device.deviceType
                    selectedGroups = selectedTypes
                    callback.setReverseData(selectedTypes, selectedGroups, countCheckeds, mDeviceLists)
                } else {
                    countCheckeds -= 1
                    if (countCheckeds == 0) {
                        selectedTypes = ""
                        selectedGroups = ""
                        callback.resetSelection()
                    } else {
                        callback.setReverseData(selectedTypes, selectedGroups, countCheckeds, mDeviceLists)
                    }
                }
            }
        }
    }

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

    override fun onBindViewHolder(holder: FiveChannelDeviceViewHolder, position: Int) {
        holder.setData(position)
    }

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

    fun setAdapterData(mDeviceList: ArrayList<SelectionModel.SelectiveDevice>, selectedType: String, selectedGroup: String, countChecked: Int) {
        this.mDeviceLists.clear()
        this.mDeviceLists.addAll(mDeviceList)
        this.selectedTypes = selectedType
        this.selectedGroups = selectedGroup
        this.countCheckeds = countChecked
        notifyDataSetChanged()
    }

    interface FiveChannelDeviceCallback {
        fun resetSelection()
        fun setReverseData(selectedType: String, selectedGroup: String, countChecked: Int, deviceList: ArrayList<SelectionModel.SelectiveDevice>)
    }
}

修改后的MainActivity的setReverseData方法

override fun setReverseData(
    selectedType: String,
    selectedGroup: String,
    countChecked: Int,
    deviceList: ArrayList<SelectionModel.SelectiveDevice>
) {
    this.selectedType = selectedType
    this.selectedGroup = selectedGroup
    this.countChecked = countChecked

    // 直接更新原始segments中的对应分组数据
    segments.find { it.channelType.equals(selectedType, true) }?.let {
        it.devices = deviceList
    }

    // 更新所有Adapter的数据
    fiveChannelDeviceAdapter.setAdapterData(segments[0].devices!!, selectedType, selectedGroup, countChecked)
    threeChannelDeviceAdapter.setAdapterData(segments[1].devices!!, selectedType, selectedGroup, countChecked)
    oneChannelDeviceAdapter.setAdapterData(segments[2].devices!!, selectedType, selectedGroup, countChecked)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 09:14:49