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
相关产品推荐
相关产品推荐

