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

Android Kotlin中RecyclerView.Adapter内ImageView更新异常问题

问题描述

我在服务器端完成设备类型图标实现后,将原临时drawable替换为服务器提供的图标,通过Coil框架加载这些图标,ImageView位于RecyclerView.Adapter中。首次加载界面时,标签与图标匹配正常;但执行排序操作后,ImageView中的图标要么与标签不对应,要么直接消失。

应用逻辑:先获取并展示服务器数据,若本地数据库数据与服务器一致则直接使用本地数据,已将服务器数据所有字段存入数据库。我添加日志验证Coil请求的图标URL,确认每个URL都是正确的。由于排序后列表长度可能与默认列表不同,我会调用notifyDatasetChanged()重绘适配器。

原适配器代码:

class ActuatorDeviceInfoAdapter(
    val ctx: FragmentActivity,
    var itemLst: MutableList<ActuatorDeviceInfo>,
    val showToggle: Boolean
) : RecyclerView.Adapter<ActuatorDeviceInfoAdapter.ViewHolder>() {
    private lateinit var binding: AdapterActuatorBinding
    var onItemClick: ((ActuatorDeviceInfo) -> Unit)? = null
    var onToggleClick: ((ActuatorDeviceInfo, Boolean, AdapterActuatorBinding) -> Unit)? = null

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

    override fun onBindViewHolder(holder: ActuatorDeviceInfoAdapter.ViewHolder, position: Int) {
        val pic = itemLst[position]
        holder.bind(pic)
    }

    override fun getItemCount(): Int = itemLst.size

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

        fun bind(actuatorDeviceInfo: ActuatorDeviceInfo) {
            binding.actuatorDevInfo = actuatorDeviceInfo

            if (showToggle) {
                binding.tglStatus.visibility = View.VISIBLE
                binding.tglStatus.isChecked = actuatorDeviceInfo.status
            } else {
                binding.tglStatus.visibility = View.GONE
            }

            setIcon(actuatorDeviceInfo.type!!)
        }

        init {
            itemView.setOnClickListener {
                onItemClick?.invoke(itemLst[adapterPosition])
            }
            binding.tglStatus.setOnClickListener {
                onToggleClick?.invoke(itemLst[adapterPosition], binding.tglStatus.isChecked, binding)
            }
        }
    }

    private fun setIcon(type: ActuatorType) {
        val iconPath = "${Utils.instance.getServerHost(ctx)}${type.iconPath}"
        binding.imgIcon.load(iconPath)
    }

    companion object{
        const val TAG = "ActuatorDeviceInfoAdapter"
    }
}
问题根源
  • 适配器定义了全局的binding变量,onCreateViewHolder每次都会覆盖这个变量,导致所有ViewHolder共用同一个binding对象。RecyclerView复用ViewHolder时,setIcon操作的是全局binding对应的视图,而非当前ViewHolder的ImageView,最终引发图标错位或消失。
  • setIcon方法属于适配器类,依赖全局binding,无法正确绑定到当前ViewHolder的视图实例。
修复方案

将binding的作用域限制在ViewHolder内部,让每个ViewHolder持有自己的binding,并把setIcon移到ViewHolder中:

class ActuatorDeviceInfoAdapter(
    val ctx: FragmentActivity,
    var itemLst: MutableList<ActuatorDeviceInfo>,
    val showToggle: Boolean
) : RecyclerView.Adapter<ActuatorDeviceInfoAdapter.ViewHolder>() {
    var onItemClick: ((ActuatorDeviceInfo) -> Unit)? = null
    var onToggleClick: ((ActuatorDeviceInfo, Boolean, AdapterActuatorBinding) -> Unit)? = null

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): ActuatorDeviceInfoAdapter.ViewHolder {
        // 直接在创建ViewHolder时生成局部binding,不再使用全局变量
        val binding = AdapterActuatorBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ActuatorDeviceInfoAdapter.ViewHolder, position: Int) {
        val deviceInfo = itemLst[position]
        holder.bind(deviceInfo)
    }

    override fun getItemCount(): Int = itemLst.size

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

        fun bind(actuatorDeviceInfo: ActuatorDeviceInfo) {
            binding.actuatorDevInfo = actuatorDeviceInfo

            if (showToggle) {
                binding.tglStatus.visibility = View.VISIBLE
                binding.tglStatus.isChecked = actuatorDeviceInfo.status
            } else {
                binding.tglStatus.visibility = View.GONE
            }

            // 调用ViewHolder自己的setIcon方法
            setIcon(actuatorDeviceInfo.type!!)
        }

        // 将setIcon移到ViewHolder内部,使用当前ViewHolder的binding
        private fun setIcon(type: ActuatorType) {
            val iconPath = "${Utils.instance.getServerHost(ctx)}${type.iconPath}"
            binding.imgIcon.load(iconPath)
        }

        init {
            itemView.setOnClickListener {
                onItemClick?.invoke(itemLst[adapterPosition])
            }
            binding.tglStatus.setOnClickListener {
                onToggleClick?.invoke(itemLst[adapterPosition], binding.tglStatus.isChecked, binding)
            }
        }
    }

    companion object{
        const val TAG = "ActuatorDeviceInfoAdapter"
    }
}

额外优化建议

  • 避免直接使用notifyDatasetChanged(),它会强制刷新所有条目,效率较低。可以使用notifyItemRangeChanged()、notifyItemMoved()等更精确的通知方法,配合DiffUtil实现局部刷新,提升性能。
  • 可以给Coil的加载请求添加占位图或错误图,避免图标加载过程中出现空白:
    binding.imgIcon.load(iconPath) {
        placeholder(R.drawable.ic_default_device)
        error(R.drawable.ic_default_device)
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 02:23:22