RecyclerView过滤后滑动ViewHolder获取Item位置异常问题
我来帮你解决这个问题!这个坑我之前也踩过,核心原因是你的Adapter在处理位置时混淆了原数据列表和过滤后的列表,导致滑动时拿到的是原列表的索引,而不是当前显示的过滤列表的位置。
先再明确下你的问题场景:
原列表是:
- 0: Apple
- 1: Banana
- 2: Mango
- 3: Coconut
- 4: Peanut
搜索"Peanut"后,它显示在列表第一位,但滑动这个Item时,viewHolder.getAdapterPosition()返回的却是0(原Apple的位置)。
问题根源
你的Adapter大概率只维护了一个原数据列表,或者在处理滑动事件时,错误地用getAdapterPosition()去对应原列表的索引。而Filter过滤后,RecyclerView显示的是过滤后的子集,但getAdapterPosition()返回的是当前Adapter数据集(也就是过滤后的列表)中的位置,你如果直接用这个位置去取原列表的元素,自然就错了。
具体解决方案
1. 给Adapter维护两个列表
首先,你的Adapter里必须同时保存原数据列表和过滤后的列表:
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> { private List<String> originalList; // 存储原始所有数据 private List<String> filteredList; // 存储过滤后要显示的数据 // 构造方法初始化原列表,同时把过滤列表初始化为原列表 public FruitAdapter(List<String> originalList) { this.originalList = new ArrayList<>(originalList); this.filteredList = new ArrayList<>(originalList); } // ... 其他ViewHolder相关代码 }
2. 正确实现Filter类
确保Filter在过滤后正确更新filteredList,并通知Adapter刷新:
private class FruitFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults results = new FilterResults(); List<String> tempFilteredList = new ArrayList<>(); if (TextUtils.isEmpty(constraint)) { // 没有过滤条件,显示全部原数据 tempFilteredList.addAll(originalList); } else { String filterText = constraint.toString().toLowerCase().trim(); for (String fruit : originalList) { if (fruit.toLowerCase().contains(filterText)) { tempFilteredList.add(fruit); } } } results.values = tempFilteredList; results.count = tempFilteredList.size(); return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { filteredList.clear(); filteredList.addAll((List<String>) results.values); notifyDataSetChanged(); // 通知Adapter更新UI } }
3. 修正滑动事件中的位置处理
在onSwiped方法中,getAdapterPosition()返回的是过滤后列表的位置,你需要用这个位置去获取过滤后的Item,再根据Item找到原列表中的位置(如果需要的话):
@Override public void onSwiped(RecyclerView.ViewHolder viewHolder, final int direction) { int filteredPos = viewHolder.getAdapterPosition(); if (filteredPos == RecyclerView.NO_POSITION) return; // 防止无效位置 // 获取当前显示的Item(过滤后的) String currentFruit = filteredList.get(filteredPos); // 如果需要原列表中的位置,这样获取 int originalPos = originalList.indexOf(currentFruit); Log.e("ORIGINAL_POSITION", "原列表位置:" + originalPos); Log.e("FILTERED_POSITION", "过滤后列表位置:" + filteredPos); // 后续处理逻辑,比如删除操作:如果是删除过滤后的Item,要同时更新原列表和过滤列表 originalList.remove(currentFruit); filteredList.remove(filteredPos); notifyItemRemoved(filteredPos); }
4. 绑定ViewHolder时用过滤后的列表
别忘了在onBindViewHolder中,也是从filteredList取数据,而不是原列表:
@Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { String fruit = filteredList.get(position); holder.fruitNameTv.setText(fruit); } @Override public int getItemCount() { return filteredList.size(); }
额外提醒
如果你的数据是复杂对象(不是简单字符串),建议在对象中添加一个字段存储原列表的位置,避免用indexOf可能带来的重复数据问题:
public class Fruit { private String name; private int originalPosition; // 存储原列表中的索引 // 构造方法、getter、setter }
这样在过滤时,每个Item都保留了原位置,滑动时直接filteredList.get(filteredPos).getOriginalPosition()就能拿到原索引,更高效也更准确。
内容的提问来源于stack exchange,提问作者IAmNotARobot

