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

时间:2014-11-02 01:52:27

标签: android android-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);
}
}

从我的recyclerview中删除有时会产生意外结果。有时会删除单击之前的元素,有时会在&#34; taskList.get(position)&#34;处发生indexOutOfBounds异常。

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

看起来RecyclerView会回收这些行,但我不会期望使用较小的数字子集来索引我的列表中的indexoutofbounds异常。

5 个答案:

答案 0 :(得分:11)

当位置发生变化时,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);
        }
    });
}

所以在你的on bind方法中,你做

// 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());
}

答案 1 :(得分:9)

像yigit所说,RecyclerView就是这样的:

A B C D

并通过

添加项目X.
mItems.add(1, X);
notifyItemInserted(1, 1);

你得到了

A X B C D

在onClickListener()中使用holder.getAdapterPosition()将为您提供要删除的数据集中的正确项目,而不是&#34;静态&#34;查看位置。这是关于它的文档onBindViewHolder

答案 2 :(得分:4)

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

答案 3 :(得分:2)

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

正如在删除项目时所说的那样,Recycler视图只是隐藏了一个项目。但通常您不想将该项留在您的收藏中。从集合中删除项目时,它会将其元素移向0&#34;而recyclerView保持相同的大小。

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

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

答案 4 :(得分:2)

感谢@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(),这条线将为您提供基础数据集的更新位置,而不是假视图。

这对我来说现在很有用,如果有人知道使用它的缺点请告诉我。希望这有助于某人。