如何在RecyclerView中正确使用DiffUtils更新ArrayList,解决点击列表崩溃问题
问题原因及修复方案
核心崩溃原因
你在点击事件中直接使用了onBindViewHolder传入的静态position参数:onBindViewHolder的position是item绑定瞬间的位置,当你通过DiffUtils更新列表(比如搜索过滤后列表长度变短、item位置发生变化),已绑定过的item内部存储的position不会同步更新,此时点击就会拿到过期的位置索引,访问列表时触发IndexOutOfBoundsException。
而notifyDataSetChanged()会强制重绘所有item,所有item的position都会重新绑定为最新值,所以不会出现崩溃。
其他潜在问题
areItemsTheSame的判断字段不合理:你当前用updatedAt作为item唯一标识,若存在多个语言项的更新时间相同,会导致DiffUtils判断item是否为同一个的逻辑出错,引发列表更新异常。建议使用唯一业务字段(比如languageCode语言编码)作为判断依据。- 额外冗余逻辑:
onBindViewHolder中判断背景的条件position == 0 && position % 2 == 0存在冗余,position == 0天然满足position % 2 ==0,可以简化。
修复代码
1. 修改Adapter点击事件的取值逻辑
把点击事件里的静态position替换为holder.adapterPosition,这个方法会实时返回item当前的最新位置:
override fun onBindViewHolder(holder: LanguageSelectionVH, position: Int) { val model = languageModelList[position] holder.binding.languageCard.apply { // 简化背景判断逻辑 setCardBackgroundColor( if (position % 2 == 0) ContextCompat.getColor(context, R.color.main_blue) else ContextCompat.getColor(context, R.color.main_green) ) } holder.binding.nativeLanguageText.text = model.languageNativeName holder.binding.originalLanguageText.text = model.languageName holder.itemView.setOnClickListener { // 用实时位置代替静态position val currentPos = holder.adapterPosition // 加边界判断,避免ViewHolder已经被回收时拿不到有效位置 if (currentPos != RecyclerView.NO_POSITION) { itemClick.langClick(currentPos) } } }
2. 修改DiffUtils的item唯一判断逻辑
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { // 用唯一的语言编码作为判断相同item的依据 return oldList[oldItemPosition].languageCode == newList[newItemPosition].languageCode }
3. 额外注意事项
每次调用setData传入的newLanguageModelList必须是新的ArrayList实例,不要和Adapter内部持有的languageModelList是同一个引用,否则DiffUtils计算新旧列表差异时会因为指向同一个集合导致计算错误。
内容的提问来源于stack exchange,提问作者Suman Radhakrishnan
相关产品推荐
相关产品推荐

