在Android中使用notifyItemRemoved或notifyDataSetChanged与RecyclerView相关

93

我正在创建一个卡片列表,使用RecyclerView进行显示。每个卡片都有一个按钮,用于将该卡片从列表中删除。

但当我使用 notifyItemRemoved() 来删除 RecyclerView 中的卡片时,它会移除该项并且动画效果良好,但是列表中的数据没有被正确更新。

相反,如果我改用 notifyDataSetChanged() ,那么列表中的项目将被正确地移除和更新,但是卡片不会出现动画效果。

有没有人在使用 notifyItemRemoved() 时有任何经验,并知道它为什么与 notifyDataSetChanged 表现不同?

这里是我正在使用的一些代码:

private List<DetectedIssue> issues = new ArrayList<DetectedIssue>();

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    // - get element from your dataset at this position
    // - replace the contents of the view with that element
    if(position >0){
        RiskViewHolder riskHolder = (RiskViewHolder)holder;
        final int index = position - 1;
        final DetectedIssue anIssue = issues.get(index);

        riskHolder.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    int index = issues.indexOf(anIssue);
                    issues.remove(anIssue);
                    notifyItemRemoved(index);

                    //notifyDataSetChanged();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

@Override
public int getItemCount() {
    return (issues.size()+1);
}

4
请尝试使用notifyItemRemoved(index+1)。 - pskink
1
索引是正确的。正如我所说,如果我使用notifyDataSetChanged()代替,一切都可以正常工作..... - revolutionary
2
你尝试过使用 notifyItemRemoved(index+1) 吗? - pskink
1
哇,这正是我的问题!谢谢你帮我省去了简化代码以使问题清晰的麻烦。 - SMBiggs
2
liist.remove(position); notifyItemRemoved(position); notifyItemRangeChanged(position, getItemCount());用于删除每次最顶部元素的代码。 - Rohit Bandil
显示剩余2条评论
8个回答

116

在 notifyItemRemoved(position); 后面使用 notifyItemRangeChanged(position, getItemCount());,不需要使用索引,只需使用位置。请参考以下代码。

private List<DetectedIssue> issues = new ArrayList<DetectedIssue>();

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    // - get element from your dataset at this position
    // - replace the contents of the view with that element
    if(position >0){
        RiskViewHolder riskHolder = (RiskViewHolder)holder;

        riskHolder.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    issues.remove(position);
                    notifyItemRemoved(position);
                    //this line below gives you the animation and also updates the
                    //list items after the deleted item
                    notifyItemRangeChanged(position, getItemCount());

                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

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

6
从文档中得知:"仅应在此方法内获取相关数据项时使用position参数,并且不应保留其副本。如果稍后需要该项的位置(例如,在单击侦听器中),请使用RecyclerView.ViewHolder.getAdapterPosition()来获取已更新的适配器位置。" - Juan Cruz Soler
1
@Akshay Mahajan,抱歉这是一条旧留言。但是仅使用“notifyItemRemoved(position);”(带有动画)已经可以很好地工作了。那么“notifyItemRangeChanged(position, getItemCount());”是做什么用的呢?我看不出任何区别。谢谢。 - Yohan Dahmani
getItemCount() - position 不应该是正确的,因为itemCount表示移除item后剩余的项数。 - Michał Ziobro
1
是的,第二个参数是更改项目的范围。使用项目计数意味着位置之后的每个项目都在更改。 - Travis Castillo
3
@YohanDahmani,如果您在尝试最后一个项目上使用notifyItemRemoved(position)方法,它将无法正常工作。您会收到IndexOutOfBoundException异常。 - Bajrang Hudda
1
这个答案毫无意义。notifyItemRemovednotifyItemRangeChanged都在做同样的事情。为什么这个答案有这么多赞是个谜。 - Manuel

39

尝试过

public void removeItem(int position) {
    this.taskLists.remove(position);
    notifyItemRemoved(position);
    notifyItemRangeChanged(position, getItemCount() - position);
}

并且运行得非常好。


8
为什么你使用getItemCount() - position而不只是getItemCount() - hamena314
@hamena314 实际上,notifyItemRangeChanged(int position, int itemCount) 表示在“position”处的项目已更改,并且从“position”开始,“itemCount”个项目已更改,因此明智的做法是仅传递“positon”之后的项目,而不是传递所有项目的列表。或者,我们可以传递getItemCount()而不是getItemCount() - position。 - Jay

19

我的错误是,notifyItemChanged(position) 是无济于事的,位置为 position 的项目可以被删除,而位置为 position+1 的项目没有问题,但是从 position+2 开始的项目会抛出异常,请在 notifyItemRemoved(position); 之后使用 notifyItemRangeChanged(position,getItemCount());

像这样:

public void removeData(int position) {
    yourdatalist.remove(position);
    notifyItemRemoved(position);
    notifyItemRangeChanged(position,getItemCount());
}

2
请提供详细信息,说明通过这样做会发生什么变化以及如何帮助解决问题。 - Viral Patel
这个已经生效了。行对象的位置也将被更新。 - ralphgabb

2

使用这个,它完美地工作。

        issues.remove(position);
        notifyItemRemoved(position);
        notifyItemRangeChanged(position, issues.size());

1
如@pskink建议的那样,在我的情况下应该是(index+1),使用notifyItemRemoved(index+1),可能是因为我保留了顶部索引即position=0用于标题。

0
在我的情况下,我使用内容提供程序和自定义的RecyclerView适配器与游标。这行代码是用来通知的:
getContext().getContentResolver().notifyChange(uri, null);

假设在您的recyclerView适配器中(删除按钮):

Uri currentUri = ContentUris.withAppendedId(DatabaseContract.ToDoEntry.CONTENT_URI_TODO, id);
int rowsDeleted = mContext.getContentResolver().delete(currentUri, null, null);
if (rowsDeleted == 0) {
    Log.d(TAG, "onClick: Delete failed");
} else {
    Log.d(TAG, "onClick: Delete Successful");
}

在你的数据库提供程序中:

case TODO_ID:
selection = DatabaseContract.ToDoEntry._ID + "=?";
selectionArgs = new String[] {String.valueOf(ContentUris.parseId(uri))};
rowsDeleted = database.delete(DatabaseContract.ToDoEntry.TODO_TABLE_NAME, selection, selectionArgs);
if (rowsDeleted != 0){
    getContext().getContentResolver().notifyChange(uri, null);
}
return rowsDeleted;

0
**my solution looks like this**

this way is unnecessary to use the heavy method:
 //notifyItemRangeChanged(xx,xx)

/**
 * 
 * recyclerView的item中的某一个view,获取其最外层的viewParent,也就是item对应的layout在adapter中的position
 *
 * @param recyclerView
 * @param view:can be the deep one inside the item,or the item itself .
 * @return
 */
public static int getParentAdapterPosition(RecyclerView recyclerView, View view, int parentId) {
    if (view.getId() == parentId)
        return recyclerView.getChildAdapterPosition(view);
    View viewGroup = (View) view.getParent();
    if (viewGroup != null && viewGroup.getId() == parentId) {
        return recyclerView.getChildAdapterPosition(viewGroup);
    }
    //recursion
    return getParentAdapterPosition(recyclerView, viewGroup, parentId);
}




//wherever you set the clickListener .
holder.setOnClickListener(R.id.rLayout_device_item, deviceItemClickListener);
holder.setOnLongClickListener(R.id.rLayout_device_item, deviceItemLongClickListener);


@Override
public boolean onLongClick(View v) {
    final int position = ViewUtils.getParentAdapterPosition(rVDevicesList, v, R.id.rLayout_device_item);
    return true;
}

0

您可以从 RecyclerView.ViewHolder 中使用 getAdapterPosition()

getLayoutPosition() 提供了布局中项目的确切位置,代码如下:

holder.removeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Position for remove
                int modPosition= holder.getAdapterPosition();
                //remove item from dataset
                numbers.remove(modPosition);
                //remove item from recycler view
                if(numbers.isEmpty())
                  notifyDataSetChanged () 
                else
                  notifyItemRemoved(modPosition);
                
            }
        });

1
你应该使用getAdapterPosition,正如文档所述,否则会产生不一致性的风险。 - marcos E.
使用notifyItemRemoved(last pos)删除ArrayList的最后一个元素时出现错误,有没有解决方案? - Arbaz.in
你能检查一下这个吗? if(numbers.isEmpty()) notifyDataSetChanged () else notifyItemRemoved(modPosition); - sree_sg

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