RecyclerView复用ViewHolder时旧数据残留,如何正确重置?
RecyclerView复用ViewHolder时动态属性残留问题优化
我的RecyclerView条目存在动态属性差异:部分条目包含距离、难度字段,部分仅含距离。复用ViewHolder时,新条目缺少的属性会保留旧条目的值,导致显示错误。
示例场景:
- 条目A:距离"10 km"、难度"Hard",两个字段正常显示
- 复用条目A的ViewHolder加载条目B(仅含距离"5 km"):难度TextView仍显示"Hard"(来自条目A的残留值)
简化示例代码
data class Item( val id: String, val baseAttributes: List<Attribute>, // 基础属性 val extraAttributes: List<Attribute> // 扩展属性 ) data class Attribute( val type: AttributeType, val value: String ) enum class AttributeType { TITLE, PHOTO, DISTANCE, DURATION, DIFFICULTY } class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val titleView: TextView = itemView.findViewById(R.id.title) private val photoView: ImageView = itemView.findViewById(R.id.photo) private val distanceView: TextView = itemView.findViewById(R.id.distance) private val durationView: TextView = itemView.findViewById(R.id.duration) private val difficultyView: TextView = itemView.findViewById(R.id.difficulty) fun bind(item: Item) { // 绑定基础属性 item.baseAttributes.forEach { attr -> when (attr.type) { AttributeType.TITLE -> titleView.text = attr.value AttributeType.PHOTO -> Glide.with(itemView).load(attr.value).into(photoView) } } // 绑定扩展属性 item.extraAttributes.forEach { attr -> when (attr.type) { AttributeType.DISTANCE -> distanceView.text = attr.value AttributeType.DURATION -> durationView.text = attr.value AttributeType.DIFFICULTY -> difficultyView.text = attr.value } } } } val items = listOf( Item( id = "1", baseAttributes = listOf( Attribute(AttributeType.TITLE, "Trail 1"), Attribute(AttributeType.PHOTO, "url1.jpg") ), extraAttributes = listOf( Attribute(AttributeType.DISTANCE, "10 km"), Attribute(AttributeType.DIFFICULTY, "Hard") // 包含难度属性 ) ), Item( id = "2", baseAttributes = listOf( Attribute(AttributeType.TITLE, "Trail 2"), Attribute(AttributeType.PHOTO, "url2.jpg") ), extraAttributes = listOf( Attribute(AttributeType.DISTANCE, "5 km") // 无难度属性 ) ) )
问题现象:
条目1的difficultyView显示"Hard";复用同一ViewHolder加载条目2时,因条目2无难度属性,difficultyView错误保留"Hard"。
不想用手动重置的方式(会让代码臃肿),希望找到更优解,比如当缺少AttributeType.DIFFICULTY时设置difficultyView.visibility = View.GONE。
优化方案
方案1:绑定前统一重置所有动态视图状态
在bind方法开头,先重置所有可能有动态变化的视图的状态(文本、可见性),确保旧数据被清空:
fun bind(item: Item) { // 重置所有动态视图的初始状态 titleView.text = "" Glide.with(itemView).clear(photoView) photoView.setImageDrawable(null) distanceView.text = "" distanceView.visibility = View.GONE durationView.text = "" durationView.visibility = View.GONE difficultyView.text = "" difficultyView.visibility = View.GONE // 绑定基础属性 item.baseAttributes.forEach { attr -> when (attr.type) { AttributeType.TITLE -> { titleView.text = attr.value titleView.visibility = View.VISIBLE } AttributeType.PHOTO -> { Glide.with(itemView).load(attr.value).into(photoView) photoView.visibility = View.VISIBLE } } } // 绑定扩展属性 item.extraAttributes.forEach { attr -> when (attr.type) { AttributeType.DISTANCE -> { distanceView.text = attr.value distanceView.visibility = View.VISIBLE } AttributeType.DURATION -> { durationView.text = attr.value durationView.visibility = View.VISIBLE } AttributeType.DIFFICULTY -> { difficultyView.text = attr.value difficultyView.visibility = View.VISIBLE } } } }
方案2:将属性列表转为Map,快速判断属性是否存在
把extraAttributes转换成以AttributeType为Key的Map,这样可以直接判断某个属性是否存在,同时简化绑定逻辑:
fun bind(item: Item) { // 转换为Map,方便快速查找 val extraAttrMap = item.extraAttributes.associateBy { it.type } // 处理距离 val distanceAttr = extraAttrMap[AttributeType.DISTANCE] if (distanceAttr != null) { distanceView.text = distanceAttr.value distanceView.visibility = View.VISIBLE } else { distanceView.visibility = View.GONE distanceView.text = "" } // 处理时长 val durationAttr = extraAttrMap[AttributeType.DURATION] if (durationAttr != null) { durationView.text = durationAttr.value durationView.visibility = View.VISIBLE } else { durationView.visibility = View.GONE durationView.text = "" } // 处理难度 val difficultyAttr = extraAttrMap[AttributeType.DIFFICULTY] if (difficultyAttr != null) { difficultyView.text = difficultyAttr.value difficultyView.visibility = View.VISIBLE } else { difficultyView.visibility = View.GONE difficultyView.text = "" } // 基础属性绑定逻辑不变 item.baseAttributes.forEach { attr -> when (attr.type) { AttributeType.TITLE -> titleView.text = attr.value AttributeType.PHOTO -> Glide.with(itemView).load(attr.value).into(photoView) } } }
方案3:封装视图绑定逻辑,避免重复代码
为每个属性类型封装对应的绑定函数,进一步简化代码,同时保证重置逻辑统一:
class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { // ... 视图初始化不变 private fun bindTextView(view: TextView, type: AttributeType, attrs: List<Attribute>) { val attr = attrs.find { it.type == type } if (attr != null) { view.text = attr.value view.visibility = View.VISIBLE } else { view.text = "" view.visibility = View.GONE } } fun bind(item: Item) { // 处理基础属性 item.baseAttributes.find { it.type == AttributeType.TITLE }?.let { titleView.text = it.value titleView.visibility = View.VISIBLE } ?: run { titleView.text = "" titleView.visibility = View.GONE } item.baseAttributes.find { it.type == AttributeType.PHOTO }?.let { Glide.with(itemView).load(it.value).into(photoView) photoView.visibility = View.VISIBLE } ?: run { Glide.with(itemView).clear(photoView) photoView.setImageDrawable(null) photoView.visibility = View.GONE } // 处理扩展属性 bindTextView(distanceView, AttributeType.DISTANCE, item.extraAttributes) bindTextView(durationView, AttributeType.DURATION, item.extraAttributes) bindTextView(difficultyView, AttributeType.DIFFICULTY, item.extraAttributes) } }
内容的提问来源于stack exchange,提问作者Alexander Zhurkin
相关产品推荐
相关产品推荐

