Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
330 views
in Technique[技术] by (71.8m points)

android - How is the position of a RecyclerView adapter related to the index of its dataset?

I thought they were the same, but they're not. The following code gives an indexOutOfBounds exception when I try to access the "position" index of my dataset, in this case a list of a model I created called 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);
}
}

Deleting from my recyclerview gives unexpected results sometimes. Sometimes the element ahead of the one clicked is deleted, other times an indexOutOfBounds exception occurs at "taskList.get(position)".

Reading https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html and https://developer.android.com/training/material/lists-cards.html did not give me any more insight into why this was happening and how to fix it.

It looks like RecyclerView recycles the rows, but I wouldn't expect an indexoutofbounds exception using a smaller subset of numbers to index my list.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

RecyclerView does not rebind views when their positions change (for obvious performance reasons). For example, if your data set looks like this:

A B C D

and you add item X via

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

to get

A X B C D

RecyclerView will only bind X and run the animation.

There is a getPosition method in ViewHolder but that may not match adapter position if you call it in the middle of an animation.

If you need the adapter position, your safest option is getting the position from the Adapter.

update for your comment

Add a Task field to the ViewHolder.

Change onCreateViewHolder as follows to avoid creating a listener object on each rebind.

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

so in your on bind method, you do

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...