从RecyclerView.Adapter中删除项目会导致OutOfBounds异常

时间:2018-08-01 16:38:08

标签: java android android-recyclerview

我正在尝试使用RecyclerView构建一个简单的列表,这将允许我添加/删除项目。

我可以删除这些项目,但是删除某个项目后,似乎未在“重新计算”职位。

例如,列表中有20个项目,如果我删除最后一个项目,则位置为19(应有的位置)。该项目将从列表中删除,但是当我再次单击以删除最后一个项目时,位置仍为19,应该为18:

D/RecycleViewTest: Remove at Position: 19
D/RecycleViewTest: Remove at Position: 19

这将导致以下异常:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.recycleviewtest, PID: 1763
    java.lang.IndexOutOfBoundsException: Index: 19, Size: 19
        at java.util.ArrayList.remove(ArrayList.java:477)
        at com.example.recycleviewtest.MainActivity$1.onRemoveClick(MainActivity.java:45)
        at com.example.recycleviewtest.SimpleViewHolder$2.onClick(SimpleViewHolder.java:42)
        at android.view.View.performClick(View.java:6205)
        at android.view.View$PerformClick.run(View.java:23653)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6682)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)

我将通过以下方式移除这些物品:

itemList.remove( position );
adapter.notifyItemRemoved( position );

我是否需要对某种函数进行额外的调用,以便列表得到“重新计算”?

这是MainActivity:

public class MainActivity extends AppCompatActivity
{
    List<SimpleViewModel> itemList;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        itemList = generateSimpleList();

        final SimpleAdapter adapter = new SimpleAdapter(itemList);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.simple_recyclerview);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setItemAnimator( new DefaultItemAnimator() );
        recyclerView.setAdapter(adapter);

        adapter.setOnItemClickListener(new SimpleAdapter.onItemClickListener() {
            @Override
            public void onItemClick(int position)
            {
                Log.d( "RecycleViewTest", "Click at Position: " + position );
                adapter.notifyItemChanged( position );
            }

            @Override
            public void onRemoveClick( int position )
            {
                Log.d( "RecycleViewTest", "Remove at Position: " + position );
                itemList.remove( position );
                adapter.notifyItemRemoved( position );
            }
        });
    }

    private List<SimpleViewModel> generateSimpleList()
    {
        List<SimpleViewModel> simpleViewModelList = new ArrayList<>();

        for (int i = 0; i < 20; i++) {
            simpleViewModelList.add(new SimpleViewModel(String.format(Locale.US, "This is item %d", i)));
        }

        return simpleViewModelList;
    }
}

这是我的RecyclerView.Adapter(SimpleAdapter):

public class SimpleAdapter extends RecyclerView.Adapter {

    private List<SimpleViewModel> models = new ArrayList<>();
    private onItemClickListener mListener;

    public SimpleAdapter(final List<SimpleViewModel> viewModels)
    {
        if (viewModels != null) {
            this.models.addAll(viewModels);
        }
    }

    public void setOnItemClickListener( onItemClickListener listener )
    {
        mListener = listener;
    }

    public interface onItemClickListener
    {
        void onItemClick( int position );
        void onRemoveClick( int position);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
        final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
        return new SimpleViewHolder(view, mListener);
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
        ((SimpleViewHolder) holder).bindData(models.get(position));
    }

    @Override
    public int getItemCount() {
        return models.size();
    }

    @Override
    public int getItemViewType(final int position) {
        return R.layout.item;
    }
}

这是我的RecyclerView.ViewHolder(SimpleViewHolder):

public class SimpleViewHolder extends RecyclerView.ViewHolder {

    private TextView  simpleTextView;
    private ImageView imgRemove;

    public SimpleViewHolder(final View itemView, final SimpleAdapter.onItemClickListener listener) {
        super(itemView);
        simpleTextView = (TextView) itemView.findViewById(R.id.simple_text);
        imgRemove      = (ImageView) itemView.findViewById(R.id.imgRemove);

        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if( listener != null )
                {
                    int position = getAdapterPosition();

                    if( position != RecyclerView.NO_POSITION )
                    {
                        listener.onItemClick( position );
                    }
                }
            }
        });

        imgRemove.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if( listener != null )
                {
                    int position = getAdapterPosition();

                    if( position != RecyclerView.NO_POSITION )
                    {
                        listener.onRemoveClick( position );
                    }
                }
            }
        });
    }

    public void bindData(final SimpleViewModel viewModel) {
        simpleTextView.setText(viewModel.getSimpleText() );
    }
}

任何提示都说明我在哪里出错了?谢谢!

更新:

我刚刚意识到,即使要播放的动画“播放过”,也没有删除任何实际项目,它们只是“重新出现”。

4 个答案:

答案 0 :(得分:2)

问题出在适配器的构造函数中:

private List<SimpleViewModel> models = new ArrayList<>();
private onItemClickListener mListener;

public SimpleAdapter(final List<SimpleViewModel> viewModels)
{
    if (viewModels != null) {
        this.models.addAll(viewModels);
    }
}

适配器不保留您传递给此构造方法的列表。而是将列表的内容复制到适配器自己的单独且不同的列表中。

这意味着以后,当您执行

@Override
public void onRemoveClick( int position )
{
    Log.d( "RecycleViewTest", "Remove at Position: " + position );
    itemList.remove( position );
    adapter.notifyItemRemoved( position );
}

该项目已从itemList中删除,但没有从适配器列表中删除。

您必须从适配器的复制列表中删除该项目,或者必须更改适配器以使其与活动共享相同的列表。可能最简单的事情是更换适配器:

private List<SimpleViewModel> models = new ArrayList<>();
private onItemClickListener mListener;

public SimpleAdapter(final List<SimpleViewModel> viewModels)
{
    if (viewModels != null) {
        this.models = viewModels; // instead of copying the contents
    }
}

答案 1 :(得分:0)

在删除项目后使用此    mRecyclerView.getRecycledViewPool()。clear();    mAdapter.notifyDataSetChanged();

答案 2 :(得分:0)

深入研究之后,似乎其他人一直在使用名为notifyItemRangeChanged的其他方法,这听起来像发生了什么事。因此,在完成itemList.remove(position);之后 尝试也做

adapter.notifyItemRemoved( position );

adapter.notifyItemRangeChanged(position, itemList.size());

答案 3 :(得分:0)

getAdapterPosition()返回1 - n的范围位置,并且您的列表具有0 - n的范围位置。

请尝试

itemList.remove( position - 1 ); //to normalize
adapter.notifyItemRemoved( position );