嵌套RecyclerView水平滚动报Inconsistency detected异常如何解决?
根因分析
你遇到的崩溃本质是嵌套RecyclerView复用过程中,子RecyclerView的数据集和布局记录的条目数量不一致触发的校验错误,具体由以下几个错误写法共同导致:
- 子RecyclerView的LayoutManager、Adapter每次在
onBindViewHolder中重复创建,复用ViewHolder时会直接重置子RV的布局状态,滚动过程中很容易出现时序错位 - 使用全局/外层Adapter的成员变量存储子Adapter的数据集
repsForEachSet,不同行绑定ViewHolder时会互相覆盖这个变量的值,子Adapter持有的数据集会被意外修改,导致布局计算时实际条目数和预期不符 - 子RecyclerView默认开启的条目预取功能在嵌套滚动场景下,会提前计算还未绑定的ViewHolder的条目位置,数据集突变时就会触发下标越界
可行解决方案
按优先级依次修改即可解决崩溃:
- 将子RecyclerView的初始化逻辑全部挪到ViewHolder的构造方法中执行,
onBindViewHolder仅做数据更新,不要每次重新设置LayoutManager和Adapter - 禁止使用全局变量存储子Adapter的数据集,每一行绑定的时候单独生成当前行的数据集实例,子Adapter自己持有独立的数据集,避免不同行的数据互相影响
- 关闭子水平RecyclerView的预取功能,在子LayoutManager初始化时调用
setItemPrefetchEnabled(false)即可针对性解决GapWorker预取抛出的崩溃
修改后代码示例
首先是ViewHolder改造:
public class WorkoutInterfaceViewHolder extends RecyclerView.ViewHolder { TextView numberOfSets; RecyclerView ChildRV; WorkoutInterfaceAdapter_Child childAdapter; public WorkoutInterfaceViewHolder(@NonNull View itemView) { super(itemView); numberOfSets = itemView.findViewById(R.id.your_textview_id); ChildRV = itemView.findViewById(R.id.child_recyclerview); // 初始化仅执行一次 LinearLayoutManager childLayoutManager = new LinearLayoutManager(itemView.getContext(), LinearLayoutManager.HORIZONTAL, false); childLayoutManager.setItemPrefetchEnabled(false); // 关闭子RV预取 ChildRV.setLayoutManager(childLayoutManager); ChildRV.setHasFixedSize(true); childAdapter = new WorkoutInterfaceAdapter_Child(itemView.getContext()); ChildRV.setAdapter(childAdapter); } }
然后是外层Adapter的onBindViewHolder改造:
@Override public void onBindViewHolder(@NonNull WorkoutInterfacesAdapter.WorkoutInterfaceViewHolder holder, int position) { // 每次bind单独获取当前数据,不要复用全局的reps、weights变量 List<String> reps = stringToArraylist(workout.numberOfReps); List<String> weights = stringToArraylist(workout.weights); try { holder.numberOfSets.setText("Set No#" + (position + 1) + ": " + weights.get(position)); // 生成当前行独有的reps列表,不要赋值给全局变量 List<Integer> currentReps = setCurrentRepsArray(reps.get(position)); // 调用子Adapter的更新方法传入当前行数据 holder.childAdapter.updateData(currentReps, workout, position); } catch (Exception e) { holder.numberOfSets.setText("Set No#" + (position + 1)); } }
最后给子Adapter新增数据更新方法:
// 子Adapter内部新增updateData方法 public void updateData(List<Integer> repsList, Workout workoutData, int parentPos) { // 子Adapter自己持有独立的数据集,不要和外部共用可变列表 this.repsForEachSet = repsList; this.workout = workoutData; this.parentPosition = parentPos; notifyDataSetChanged(); }
额外注意事项
- 确保
setCurrentRepsArray方法每次返回新的ArrayList实例,不要复用同一个全局List对象,避免多个子Adapter共用同一份数据集 - 如果外层列表需要更新数据,优先使用
notifyItemChanged、notifyItemRangeInserted等局部更新方法,减少全局notifyDataSetChanged的使用
内容的提问来源于stack exchange,提问作者levi
相关产品推荐
相关产品推荐

