RecyclerView 的 notifyItemChanged(position) 导致应用崩溃,而使用 notifyDataSetChanged() 没有动画效果。

3
所以我有一个RecyclerView,每个xml都有一个复选框,当按下时,它会从RecyclerView中删除该特定项。这至少是我试图做的。而且据我所知,为了使其正常工作,我需要使用notifyItemChanged(position);(就在底部)。
然而,当我使用notifyItemChanged(position);时,仅在按以下顺序的复选框时才会正确删除项目:1,2,3,4,5或5,4,3,2,1。如果我想要删除第一个项目,它会将其删除,但是如果我想要删除第三个项目,则会删除第四个。
我发现的另一个选项是使用notifyDataSetChanged();并且它可以按任何顺序工作,但是那么没有显示动画。
我希望在添加和删除项目时有动画效果,因此如果有一种方法可以使用notifyDataSetChanged();实现这一点,我需要帮助。
public class ListAdapter extends RecyclerView.Adapter{

SharedPreferences sharedpreferences, prefs;
public static final String MyPREFERENCES = "MyPrefs";
public static final String date = "date";
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.nd_table, parent, false);
    return new ListViewHolder(view);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int adapterPosition) {
    //((ListViewHolder) holder).bindView(position);
    String data = hwName.get(adapterPosition);
    String data2 = hwDesc.get(adapterPosition);
    ((ListViewHolder) holder).mItemText.setText(data);
    ((ListViewHolder) holder).mItemText2.setText(data2);
    ((ListViewHolder) holder).buttonDelete.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(View view){
            removeItem(adapterPosition);
            notifyItemChanged(adapterPosition);
        }
    });
}


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

public class ListViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

    private TextView mItemText, mItemText2;
    public CheckBox buttonDelete;

    public ListViewHolder(View itemView) {
        super(itemView);
        mItemText = (TextView) itemView.findViewById(R.id.pam_name);
        mItemText2 = (TextView) itemView.findViewById(R.id.pam_nd);
        buttonDelete = (CheckBox) itemView.findViewById(R.id.nd_checkbox);
        itemView.setOnClickListener(this);
    }

    public void bindView(int position) {
        String value = hwName.get(position);
        String value2 = hwDesc.get(position);

        mItemText.setText(value);
        mItemText2.setText(value2);

        //notifyItemInserted(hwName.size());
    }


    public void onClick(View view) {

    }
}

public void removeItem(int position){
    hwName.remove(position);
    hwDesc.remove(position);
    //notifyDataSetChanged();
    notifyItemRemoved(position);
    notifyItemChanged(position);
}}

崩溃报告:

致命异常:主线程 进程:com.sajev.slush,PID:14166 java.lang.IndexOutOfBoundsException: 索引:5,大小:5 at java.util.ArrayList.remove(ArrayList.java:477) at com.sajev.slush.ListAdapter.removeItem(ListAdapter.java:89) at com.sajev.slush.ListAdapter$1.onClick(ListAdapter.java:45) at android.view.View.performClick(View.java:5646) at android.widget.CompoundButton.performClick(CompoundButton.java:123) at android.view.View$PerformClick.run(View.java:22459) at android.os.Handler.handleCallback(Handler.java:761) at android.os.Handler.dispatchMessage(Handler.java:98) at android.os.Looper.loop(Looper.java:156) at android.app.ActivityThread.main(ActivityThread.java:6523) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:941) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:831)

注:此报告显示应用程序在执行过程中遇到了一个问题,导致程序崩溃。具体问题是由于数组越界所致,即尝试访问不存在的数组元素。

发布崩溃日志 - ADM
@ADM 好的,已完成。 - Sajev
java.lang.IndexOutOfBoundsException。调试你的代码。 - ADM
@ADM 我有点理解为什么会崩溃,比如我删除第一个项目并且第二个也更新了,这里没有问题。但是如果我现在按下删除第三个按钮,它将删除第四个。但如果第四个已经移动到第三个位置hwName.remove(position);和hwDesc.remove(position);就不能执行删除操作,它将会崩溃。我之前知道这一点,但我不知道如何像notifyDataSetChanged()一样更新所有内容。 - Sajev
1个回答

4
有两个问题:

在调用 notifyItemRemoved() 后不要再调用 notifyItemChanged()

调用 notifyItemRemoved() 会告诉适配器该位置上的项已经不存在了,这就是你需要做的全部。而调用 notifyItemChanged() 则表示该位置的项已被修改(例如其名称已更改,但没有其他更改),因此当你删除一项时并不适合使用它。
你会遇到崩溃,因为你按顺序调用了这两个方法。想象一下,你的列表中只有一个项,然后将其删除。当你调用 notifyItemRemoved() 时,适配器现在知道你的列表中已经没有任何项了。但接着你又调用了 notifyItemChanged(),于是适配器试图获取第一个项……但是列表为空,所以你会崩溃。

onBindViewHolder() 中传递的 position 参数不能是 final

(参见 https://www.youtube.com/watch?v=LqBlYJTfLP4 的 ~43:10 获取更多信息)
编译器无法阻止你向 onBindViewHolder()position 参数添加 final 关键字,但这样做是逻辑错误的。当你调用诸如 notifyItemRemoved()notifyItemInserted() 的方法时,其他视图持有者不会重新绑定,因此 position 参数将不再反映实际情况。
不要在单击侦听器中使用该 position 参数。而是在运行时查找它。以下是一些更新后的代码,同时解决上述两个问题:
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
    ...
    ((ListViewHolder) holder).buttonDelete.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(View view){
            removeItem(holder.getAdapterPosition());
        }
    });
}

public void removeItem(int position){
    hwName.remove(position);
    hwDesc.remove(position);
    notifyItemRemoved(position);
}

你看,问题还在那里。想象一下有3个项目,在第一次点击时,我可以毫无问题地删除任何一个,所以假设我删除了第一个。现在我想删除第二个项目,它已经移动到第一个的位置,但仍被视为第二个,并调用要删除第二个的位置,但因为它已经移动到第一个的位置,实际上删除的是第三个,因为第三个已经移动到第二个的位置。 - Sajev
@Sajev 我更新了我的答案,解释了为什么在视图持有者事件监听器中不应使用 final int position - Ben P.
哦,伙计,非常感谢,现在它能够正常工作了。另外,也许你有任何关于这个小错误的想法。如果我选中第一项,然后往下5或6项,另一个项目的复选框会被选中。实际上并没有激活移除,只是视觉效果。我不太确定是什么原因导致的。 - Sajev
@Sajev 我的最佳猜测是(1)用户单击复选框(2)项被删除(3)旧视图持有者被回收(4)回收的视图持有者用于显示不同的项(5)因为复选框状态在 onBindViewHolder() 中从未取消设置,它保留了来自 #1 的选中状态。 - Ben P.
你是对的,好的,我现在已经修复了。再次感谢 :) - Sajev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接