RecyclerView插入元素显示异常:层级列表展开时视图绘制错误
嘿,我帮你分析下这个问题——这种展开元素时最后一个视图绘制异常、滚动后又恢复正常的情况,基本都是RecyclerView的视图复用机制或者数据更新通知逻辑出了问题。结合你的代码来看,有几个关键地方可以调整:
1. 立刻修复getItemViewType的实现
你现在直接返回position作为视图类型,这完全违背了RecyclerView视图复用的设计初衷!RecyclerView会认为每个位置的视图都是独一无二的,不仅会浪费性能,更会在列表插入/删除元素后,因为position变化导致视图类型匹配混乱,直接引发绘制错误。
正确的做法是根据数据的实际类型来返回视图类型,比如区分父项和子项:
@Override public int getItemViewType(int position) { // 假设你的数据模型里有判断是否为父项的方法 ElementObject item = elementObjectsLive.get(position); return item.isParentItem() ? 0 : 1; // 父项用类型0,子项用类型1 }
如果所有父项和子项的布局完全一致,那直接返回0就行,绝对不能返回position!
2. 把展开/折叠的逻辑从onBindViewHolder里移出来
你在onBindViewHolder里调用notifyItemRangeInserted/notifyItemRangeRemoved是个大忌——这会触发RecyclerView重新绑定视图,很容易导致循环调用和布局计算混乱。应该把点击监听放到ViewHolder的构造方法里:
class RecyclerviewViewHolder extends RecyclerView.ViewHolder { RecyclerviewViewHolder(View pView1) { super(pView1); // 给父项视图设置点击事件 itemView.setOnClickListener(v -> { int currentPos = getAdapterPosition(); if (currentPos == RecyclerView.NO_POSITION) return; // 避免无效位置 ElementObject parentItem = elementObjectsLive.get(currentPos); if (parentItem.isExpanded()) { // 折叠:移除对应的子项 int childCount = parentItem.getChildList().size(); elementObjectsLive.subList(currentPos + 1, currentPos + 1 + childCount).clear(); notifyItemRangeRemoved(currentPos + 1, childCount); } else { // 展开:插入子项到父项后面 elementObjectsLive.addAll(currentPos + 1, parentItem.getChildList()); notifyItemRangeInserted(currentPos + 1, parentItem.getChildList().size()); } // 更新父项的展开状态,刷新父项UI(比如展开/折叠图标) parentItem.setExpanded(!parentItem.isExpanded()); notifyItemChanged(currentPos); }); } }
这样点击逻辑和视图绑定彻底分开,不会干扰RecyclerView的正常布局流程。
3. 修正setHasStableIds(true)的配套实现
你开启了setHasStableIds(true),但getItemId返回的是position,这等于没开!stableId的作用是让RecyclerView通过数据的唯一标识来复用视图,而不是变化的position。你应该返回数据项的唯一ID:
@Override public long getItemId(int position) { // 假设你的数据模型有一个唯一的id字段 return elementObjectsLive.get(position).getUniqueId(); }
如果你的数据没有唯一ID,那干脆去掉setHasStableIds(true),返回position的stableId反而会帮倒忙。
4. 额外的小提醒
- 确保
elementObjectsLive是可变的列表(比如ArrayList),而且所有的增删操作都在主线程执行(毕竟是UI相关的操作) - 如果后续用了
DiffUtil,一定要正确实现areItemsTheSame和areContentsTheSame方法,这能让RecyclerView更精准地更新视图,避免类似的绘制问题
按照这些步骤调整后,展开/折叠时的视图绘制错误应该就能解决了。
内容的提问来源于stack exchange,提问作者Ali Has

