RecyclerView滚动触发IndexOutOfBoundsException问题求助
你遇到的java.lang.IndexOutOfBoundsException: Invalid index 49, size is 0错误,核心原因是RecyclerView的Adapter在数据源items已经为空的情况下,仍然尝试去访问位置49的数据——这时候集合里没有任何元素,自然就抛出越界异常了。我来帮你拆解可能的原因和对应的解决办法:
一、先定位核心问题点
你的Adapter里这段代码是直接触发异常的地方:
return items.get(position) != null ? VIEW_ITEM : VIEW_PROG;
当items集合的size为0时,items.get(49)肯定会报错——因为集合里根本没有第49个元素。而RecyclerView会请求这个position,说明它还“以为”数据源里有这么多元素,这通常是数据源变更后没及时通知Adapter导致的。
二、具体解决方案
1. 数据源变更必须同步通知Adapter
每次修改items集合(清空、添加、删除元素)后,一定要调用Adapter的通知方法,让RecyclerView更新UI状态,避免它继续绑定旧的position数据。比如:
// 清空数据源时 items.clear(); rcAdapter.notifyDataSetChanged(); // 这行绝对不能少! // 或者更新数据源时(更细粒度的通知,性能更好) int startPosition = items.size(); items.addAll(newData); rcAdapter.notifyItemRangeInserted(startPosition, newData.size());
如果只用items.clear()却不通知Adapter,RecyclerView还会保留之前的item数量,滚动时就会请求超出当前数据源范围的position。
2. 修复getItemViewType的安全判断逻辑
你现在的getItemViewType方法没有先判断position是否合法,直接去访问items.get(position),这在数据源为空时必然报错。修改成下面这样,先做边界检查:
@Override public int getItemViewType(int position) { // 先判断position是否在当前数据源的有效范围内 if (position >= items.size()) { return VIEW_PROG; // 或者根据你的逻辑返回对应的类型 } // 再去判断item是否为空 return items.get(position) != null ? VIEW_ITEM : VIEW_PROG; }
另外,你Activity里的SpanSizeLookup中调用了rcAdapter.getItemViewType(position),这里也要确保当数据源变化时,Lookup能拿到最新的状态——最好不要在Lookup里直接依赖Adapter的方法,或者确保调用时Adapter的数据源是有效的。
3. 处理异步加载的竞态问题
如果你的数据是异步加载的(比如网络请求),很可能出现“旧的加载回调在数据源已经被清空后才执行”的情况,导致Adapter拿到过时的position。解决办法是:
- 用生命周期绑定的方式管理异步任务,比如用
CoroutineScope(Kotlin)或者Disposable(RxJava),在Activity销毁时取消旧的请求。 - 在更新数据源前,先检查当前页面是否还处于活跃状态,避免无效的更新。
4. 检查getItemCount的返回值
如果你的Adapter有加载更多的逻辑,getItemCount可能返回items.size() + 1(多一个加载更多的item)。这时候要确保:当items为空时,不要返回1以外的数值,或者在getItemViewType里处理好边界情况,避免请求超出范围的position。
三、完整的Adapter示例片段
给你一个更安全的Adapter写法参考:
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private static final int VIEW_ITEM = 0; private static final int VIEW_PROG = 1; private List<YourItemModel> items = new ArrayList<>(); private boolean hasMoreData = true; // 标记是否还有更多数据要加载 // 统一的数据源更新方法,自动通知Adapter public void updateItems(List<YourItemModel> newItems) { items.clear(); items.addAll(newItems); notifyDataSetChanged(); } public void clearItems() { items.clear(); hasMoreData = true; // 清空后重置加载状态 notifyDataSetChanged(); } public void setHasMoreData(boolean hasMore) { this.hasMoreData = hasMore; notifyDataSetChanged(); } @Override public int getItemViewType(int position) { // 先做边界判断,避免越界 if (position >= items.size()) { return hasMoreData ? VIEW_PROG : VIEW_ITEM; // 没有更多数据时不显示加载框 } return items.get(position) != null ? VIEW_ITEM : VIEW_PROG; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); if (viewType == VIEW_ITEM) { return new ItemViewHolder(inflater.inflate(R.layout.item_layout, parent, false)); } else { return new ProgressViewHolder(inflater.inflate(R.layout.progress_layout, parent, false)); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { // 绑定数据前再次检查position合法性 if (position >= items.size()) { // 处理加载更多的逻辑,比如触发下一页请求 return; } YourItemModel item = items.get(position); // 绑定item数据到ViewHolder ((ItemViewHolder) holder).bind(item); } @Override public int getItemCount() { // 只有在需要显示加载更多时才+1,否则直接返回items.size() return items.size() + (hasMoreData ? 1 : 0); } // 定义你的ViewHolder类 private static class ItemViewHolder extends RecyclerView.ViewHolder { // ... ViewHolder的控件引用 public void bind(YourItemModel item) { // ... 绑定逻辑,比如设置文本、图片等 } } private static class ProgressViewHolder extends RecyclerView.ViewHolder { // ... 加载更多的ViewHolder控件引用,比如ProgressBar } }
内容的提问来源于stack exchange,提问作者Fajar

