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



我以为它们是一样的,但事实并非如此。当我试图访问数据集的"位置"索引时,以下代码给出了indexOutOfBounds异常,在这种情况下,是我创建的名为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)"处会发生indexOutOfBounds异常。

阅读https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html和https://developer.android.com/training/material/lists-cards.html没有让我更多地了解为什么会发生这种情况以及如何解决

看起来RecyclerView回收了行,但我不希望出现indexoutofbounds异常,使用较小的数字子集来索引我的列表。

RecyclerView在视图位置发生变化时不会重新绑定视图(出于明显的性能原因)。例如,如果您的数据集如下所示:

A B C D

然后你通过添加项目X

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

获取

A X B C D

RecyclerView将仅绑定X并运行动画。

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

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

更新您的评论

将"任务"字段添加到ViewHolder中。

按如下方式更改onCreateViewHolder,以避免在每次重新绑定时创建侦听器对象。

// 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的工作原理是:

A B C D

然后你通过添加项目X

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

你得到

A X B C D

在onClickListener()中使用holder.getAdapterPosition()将为您提供要删除的数据集中的正确项,而不是"静态"视图位置。这是BindViewHolder 上关于它的文档

为什么不使用公共界面来点击按钮并控制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();
            } 
        }
    });

感谢@yigit的回答,他的解决方案主要有效,我只是稍微调整了一下,以避免使用我不知道如何实现的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(),这一行将从底层数据集而不是伪视图中为您提供更新的位置。

到目前为止,这对我来说是有效的,如果有人知道使用它的缺点,请告诉我。希望这能帮助到别人。

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

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

如果您致电taskList.remove(position);,则必须再次评估您的职位:

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

最新更新