RecyclerView多条目点击末尾项返回错误位置并抛空指针问题求助
嘿,我帮你找出了导致RecyclerView点击错位和NullPointerException的几个关键问题,咱们一个个解决:
你的ViewHolder把headText和urlText声明成了static变量!RecyclerView的ViewHolder是会被复用的,静态变量属于类而非单个实例,所有ViewHolder会共享这两个TextView引用——这会直接导致:
- 滚动时条目内容乱跳、显示错误
- 点击时获取的文本根本不是当前点击条目的内容
- 间接引发position匹配错误
修复方法:把静态修饰符去掉,改成实例变量,同时把操作视图的方法也改成实例方法:
修改后的ViewHolder代码:
class ViewHolder extends RecyclerView.ViewHolder { private RecyclerViewOnTouchItemListener.ClickListener clickListener; // 去掉static,改为实例变量 private TextView headText, urlText; private ConstraintLayout itemLayout; public ViewHolder(View itemView) { super(itemView); headText = itemView.findViewById(R.id.list_item_header); urlText = itemView.findViewById(R.id.list_item_content); itemLayout = itemView.findViewById(R.id.item_list); } // 去掉static,改为实例方法,操作当前ViewHolder的视图 public void setHeadText(String headText) { this.headText.setText(headText); } public void setScoreText(String scoreText) { this.urlText.setText(scoreText); } public String getHeadText(){ return headText.getText().toString(); } public String getUrlText(){ return urlText.getText().toString(); } }
同时更新Adapter的onBindViewHolder方法,调用实例方法而不是静态方法:
@Override public void onBindViewHolder(ViewHolder holder, int position) { ItemView i = items.get(position); // 调用holder的实例方法 holder.setHeadText(i.getHead()); holder.setScoreText(i.getContent()); }
getChildAt(position)导致空指针 在Activity的setItemSelectedState里,你用listOfUrl.getChildAt(position)来获取条目视图,但这个方法的参数是当前屏幕可见子View的索引,不是Adapter的position!当列表条目超过屏幕可见数量时,Adapter的position会远大于可见子View的数量,此时getChildAt(position)会返回null,直接抛出NullPointerException。
修复方法:直接用onClick方法传入的view参数(它就是被点击的条目根布局)来修改背景色,同时修正selectedItems的更新方式:
修改setItemSelectedState方法:
private void setItemSelectedState(boolean isSelected, int position, int color, View itemView) { try { // 直接操作点击的条目视图 itemView.setBackgroundColor(color); // 用set更新状态,而不是add(add是插入新元素,会导致列表长度混乱) selectedItems.set(position, isSelected); } catch (NullPointerException e) { e.printStackTrace(); } }
然后在onClick中传入view:
@Override public void onClick(View view, int position) { if (selectedItems.get(position)) { setItemSelectedState(false, position, Color.WHITE, view); } else { setItemSelectedState(true, position, Color.LTGRAY, view); } }
你初始化selectedItems时的写法容易出问题,而且更新时用add方法会导致列表长度不断增加,和Adapter的条目数量不匹配,后续获取状态时会出现索引越界或者状态错误。
修复方法:初始化时直接创建和条目数量一致的列表,填充默认状态:
// 初始化一个和Adapter条目数相同的列表,默认都是false selectedItems = new ArrayList<>(Collections.nCopies(adapter.getItemCount(), false));
虽然你写的RecyclerViewOnTouchItemListener能工作,但更推荐在Adapter内部设置点击监听——这样代码更内聚,还能避免列表过滤、排序时的position错位问题。
比如给Adapter添加点击接口:
public class ListAdapter extends RecyclerView.Adapter<ViewHolder> { private List<ItemView> items; private Context context; private OnItemClickListener listener; // 新增构造方法传入监听 public ListAdapter(List<ItemView> items, Context context, OnItemClickListener listener) { this.items = items; this.context = context; this.listener = listener; } // ... 其他原有代码 ... @Override public void onBindViewHolder(ViewHolder holder, int position) { ItemView i = items.get(position); holder.setHeadText(i.getHead()); holder.setScoreText(i.getContent()); // 给条目根布局设置点击监听 holder.itemLayout.setOnClickListener(v -> { if (listener != null) { listener.onItemClick(v, position); } }); } // 定义点击接口 public interface OnItemClickListener { void onItemClick(View view, int position); } }
然后在Activity中初始化Adapter时直接传入监听逻辑:
adapter = new ListAdapter(fillList(), context, (view, position) -> { if (selectedItems.get(position)) { setItemSelectedState(false, position, Color.WHITE, view); } else { setItemSelectedState(true, position, Color.LTGRAY, view); } });
这样就不需要额外的RecyclerViewOnTouchItemListener了,代码更简洁可靠。
内容的提问来源于stack exchange,提问作者Mostafa Qadoomi

