RecyclerView适配器的位置如何与其数据集的索引相关联?

我以为他们是一样的,但他们不是。 当我尝试访问我的数据集的“位置”索引时,下面的代码给出了一个indexOutOfBoundsexception,在这种情况下,我创build了一个名为Task的模型列表:

public class TaskAdapter extends RecyclerView.Adapter<TaskAdapter.TaskViewHolder> { private List<Task> taskList; private TaskAdapter thisAdapter = this; // cache of views to reduce number of findViewById calls public static class TaskViewHolder extends RecyclerView.ViewHolder { protected TextView taskTV; protected ImageView closeBtn; public TaskViewHolder(View v) { super(v); taskTV = (TextView)v.findViewById(R.id.taskDesc); closeBtn = (ImageView)v.findViewById(R.id.xImg); } } public TaskAdapter(List<Task> tasks) { if(tasks == null) throw new IllegalArgumentException("tasks cannot be null"); taskList = tasks; } // onBindViewHolder binds a model to a viewholder @Override public void onBindViewHolder(TaskViewHolder taskViewHolder, int pos) { final int position = pos; Task currTask = taskList.get(pos); taskViewHolder.taskTV.setText(currTask.getDescription()); **taskViewHolder.closeBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("TRACE", "Closing task at position " + position); // delete from SQLite DB Task taskToDel = taskList.get(position); taskToDel.delete(); // updating UI taskList.remove(position); thisAdapter.notifyItemRemoved(position); } });** } @Override public int getItemCount() { //Log.d("TRACE", taskList.size() + " tasks in DB"); return taskList.size(); } // inflates row to create a viewHolder @Override public TaskViewHolder onCreateViewHolder(ViewGroup parent, int pos) { View itemView = LayoutInflater.from(parent.getContext()). inflate(R.layout.list_item, parent, false); Task currTask = taskList.get(pos); //itemView.setBackgroundColor(Color.parseColor(currTask.getColor())); return new TaskViewHolder(itemView); } } 

从我的回收站删除有时会产生意想不到的结果。 有时,点击之前的元素将被删除,而其他时候在“taskList.get(position)”会发生indexOutOfBoundsexception。

阅读https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html和https://developer.android.com/training/material/lists-cards.html没有给我更深入了解为什么会发生这种情况,以及如何解决这个问题。

它看起来像RecyclerView回收行,但我不会指望indexoutofbounds例外使用一个较小的数字子集来索引我的列表。

Related of "RecyclerView适配器的位置如何与其数据集的索引相关联?"

RecyclerView在其位置改变时不会重新绑定视图(出于明显的性能原因)。 例如,如果你的数据集是这样的:

 ABCD 

并通过添加项目X.

 mItems.add(1, X); notifyItemInserted(1, 1); 

要得到

 AXBCD 

RecyclerView将只绑定X并运行animation。

在ViewHolder中有一个getPosition方法,但如果在animation中间调用它,则可能与适配器位置不匹配。

如果您需要适配器位置,最安全的选项是从适配器获取位置。

更新您的评论

将任务字段添加到ViewHolder。

按如下所示更改onCreateViewHolder ,以避免在每个重新绑定中创build一个侦听器对象。

 // inflates row to create a viewHolder @Override public TaskViewHolder onCreateViewHolder(ViewGroup parent, int type) { View itemView = LayoutInflater.from(parent.getContext()). inflate(R.layout.list_item, parent, false); final TaskViewHolder vh = new TaskViewHolder(itemView); taskViewHolder.closeBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // delete from SQLite DB Task taskToDel = vh.getTask(); final int pos = taskList.indexOf(taskToDel); if (pos == -1) return; taskToDel.delete(); // updating UI taskList.remove(pos); thisAdapter.notifyItemRemoved(pos); } }); } 

所以在你的绑定方法,你这样做

 // onBindViewHolder binds a model to a viewholder @Override public void onBindViewHolder(TaskViewHolder taskViewHolder, int pos) { Task currTask = taskList.get(pos); taskViewHolder.setTask(currTask); taskViewHolder.taskTV.setText(currTask.getDescription()); } 

像yigit说的,RecyclerView的工作原理是这样的:

 ABCD 

并通过添加项目X.

 mItems.add(1, X); notifyItemInserted(1, 1); 

你得到

 AXBCD 

holder.getAdapterPosition()中使用holder.getAdapterPosition()会给你从数据集中删除正确的项目,而不是“静态”的视图位置。 这是关于它在onBindViewHolder的文档

为什么不使用公共接口来点击button,并控制MainActivity中的操作。

在你的适配器中添加:

 public interface OnItemClickListener { void onItemClick(View view, int position, List<Task> mTaskList); } 

 public OnItemClickListener mItemClickListener; // Provide a suitable constructor (depends on the kind of dataset) public TaskAdapter (List<Task> myDataset, OnItemClickListener mItemClickListener) { this.mItemClickListener = mItemClickListener; this.mDataset = mDataset; } 

再加上ViewHolder类中的调用

  public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { public ViewHolder(View v) { super(v); ... closeBtn = (ImageView)v.findViewById(R.id.xImg); closeBtn.setOnClickListener(this); } @Override public void onClick(View v) { // If not long clicked, pass last variable as false. mItemClickListener.onItemClick(v, getAdapterPosition(), mDataset); } } 

在您的MainActivity中更改您的适配器来处理呼叫

 // set Adapter mAdapter = new TaskAdapter(taskList, new TaskAdapter.OnItemClickListener() { @Override public void onItemClick(View v, int position) { if (v.getId() == R.id.xImg) { Task taskToDel = taskList.get(position); // updating UI taskList.remove(position); thisAdapter.notifyItemRemoved(position); // remove from db with unique id to use delete query // dont use the position but something like taskToDel.getId() taskToDel.delete(); } } }); 

就个人而言,我不喜欢这个RecyclerViews的概念。 似乎没有完全想到。

正如在删除一个项目时所说的,Recycler视图只是隐藏了一个项目。 但是通常你不想把这个物品留在你的藏品中。 当从集合中删除一个项目时,它将元素移向0,而recyclerView保持相同的大小。

如果你正在调用taskList.remove(position); 你的位置必须再次评估:

 int position = recyclerView.getChildAdapterPosition(taskViewHolder.itemView); 

感谢@yigit的回答,他的解决scheme主要工作,我只是稍微调整了一下,以避免使用vh.getTask(),我不知道如何实现。

  final ViewHolder vh = new ViewHolder(customView); final KittyAdapter final_copy_of_this = this; // We attach a CheckChange Listener here instead of onBindViewHolder // to avoid creating a listener object on each rebind // Note Rebind is only called if animation must be called on view (for efficiency) // It does not call on the removed if the last item is checked vh.done.setChecked(false); vh.done.setOnCheckedChangeListener(null); vh.done.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { buttonView.setEnabled(false); final int pos2 = vh.getAdapterPosition(); // THIS IS HOW TO GET THE UPDATED POSITION // YOU MUST UPDATE THE DATABASE, removed by Title DatabaseHandler db = new DatabaseHandler(mContext); db.remove(mDataSet.get(pos2).getTitle(), fp); db.close(); // Update UI mDataSet.remove(pos2); final_copy_of_this.notifyItemRemoved(pos2); } }); 

注意,为了获得更新的位置,你可以调用vh.getAdapterPosition(),这个行会给你从底层数据集更新的位置,而不是假的视图。

到目前为止,这是我的工作,如果有人知道使用这个缺点,请让我知道。 希望这有助于某人。