Verwenden von notifyItemRemoved oder notifyDataSetChanged mit RecyclerView in Android

78

Ich erstelle eine Liste von Karten, die mit RecyclerView angezeigt werden sollen. Jede Karte verfügt über eine Schaltfläche zum Entfernen dieser Karte aus der Liste.

Wenn ich notifyItemRemoved () verwende , um die Karte in der RecyclerView zu entfernen, wird das Element entfernt und animiert, aber die Daten in der Liste werden nicht korrekt aktualisiert.

Wenn ich stattdessen zu notifyDataSetChanged () wechsle, werden die Elemente in der Liste entfernt und korrekt aktualisiert, aber dann werden die Karten nicht animiert.

Hat jemand Erfahrung mit der Verwendung von notifyItemRemoved () und weiß, warum es sich anders verhält als notifyDataSetChanged?

Hier ist ein Stück Code, den ich benutze:

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);
}
Revolutionär
quelle
3
versuchen Sie notifyItemRemoved (Index + 1)
pskink
1
Der Index ist korrekt. Wie gesagt, alles funktioniert gut, wenn ich stattdessen notifyDataSetChanged () verwende .....
revolutionär
2
Haben Sie versucht, notifyItemRemoved (Index + 1)?
Pskink
1
Wow, mein genaues Problem! Vielen Dank, dass Sie mir die Mühe erspart haben, meinen Code zu vereinfachen, um die Frage klar zu stellen.
SMBiggs
2
liist.remove (Position); notifyItemRemoved (Position); notifyItemRangeChanged (position, getItemCount ()); Zum Entfernen jedes obersten Elements.
Rohit Bandil

Antworten:

104

Verwenden Sie notifyItemRangeChanged (position, getItemCount ()); nach notifyItemRemoved (Position);
Sie müssen keinen Index verwenden, sondern nur die Position. Siehe Code unten.

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();
}
Akshay Mahajan
quelle
3
Aus der Dokumentation: "Sie sollten den Positionsparameter nur beim Erfassen des zugehörigen Datenelements innerhalb dieser Methode verwenden und keine Kopie davon aufbewahren. Wenn Sie die Position eines Elements später benötigen (z. B. in einem Klick-Listener), verwenden Sie RecyclerView. ViewHolder.getAdapterPosition () mit der aktualisierten Adapterposition. "
Juan Cruz Soler
1
@Akshay Mahajan Alter Thread sorry, notifyItemRemoved(position);funktioniert aber gut alleine (mit Animation). Was notifyItemRangeChanged(position, getItemCount());macht das Ich kann den Unterschied nicht sehen. Vielen Dank
Yohan Dahmani
Sollte nicht getItemCount () - Position sein, da itemCount die Anzahl der Elemente nach dem entfernten Element bedeutet?
Michał Ziobro
1
Ja, der zweite Parameter ist der Bereich der geänderten Elemente. Die Verwendung der Artikelanzahl bedeutet, dass sich jeder Artikel nach dem Ändern der Position ändert.
Travis Castillo
2
@YohanDahmani, notifyItemRemoved (Position); funktioniert nicht, wenn Sie das letzte Element anprobieren. IndexOutOfBoundException, die Sie erhalten werden.
Bajrang Hudda
30

Versucht

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

und wie ein Zauber arbeiten.

Grender
quelle
6
Können Sie erklären, warum Sie getItemCount() - positionstatt nur verwenden getItemCount()?
hamena314
@ hamena314 notifyItemRangeChanged (int position, int itemCount) erwähnt, dass sich an "position" Elemente geändert haben und von "position" haben sich "itemCount" Elemente geändert, so dass es ratsam ist, nur Elemente nach "position" zu übergeben, anstatt die Liste aller zu übergeben die Gegenstände. Oder stattdessen "getItemCount () - position" übergeben, können wir getItemCount () übergeben.
Jay
15

Mein Fehler, notifyItemChanged (Position) ist hilflos, das Positionselement kann entfernt werden und das Positionselement + 1 ist in Ordnung, aber die Elemente beginnen bei Position + 2. Sie erhalten eine Ausnahme. Bitte verwenden Sie notifyItemRangeChanged (Position, getItemCount ()); nach notifyItemRemoved (Position);

so was:

public void removeData(int position) {
    yourdatalist.remove(position);
    notifyItemRemoved(position);
    notifyItemRangeChanged(position,getItemCount());
}
TikT
quelle
2
Bitte geben Sie an, was sich dadurch ändert und wie das Problem behoben werden kann.
AndroidMechanic - Viral Patel
1

Wie @pskink vorgeschlagen hat, sollte es in meinem Fall mit (Index + 1) sein notifyItemRemoved(index+1), wahrscheinlich weil ich den obersten Index reserviere, dh position=0für einen Header.

Revolutionär
quelle
0

Sie können getLayoutPosition()aus dem verwendenRecyclerView.ViewHolder

getLayoutPosition() gibt die genaue Position des Elements im Layout an und Code ist

holder.removeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Position for remove
                int modPosition= holder.getLayoutPosition();
                //remove item from dataset
                numbers.remove(modPosition);
                //remove item from recycler view
                notifyItemRemoved(modPosition);
            }
        });
sree_sg
quelle
1
Sie sollten getAdapterPosition verwenden, wie in der Dokumentation angegeben . Es besteht die Gefahr, dass Inkonsistenzen entstehen.
Marcos E.
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;
}
Dong Sheng
quelle
0

In meinem Fall verwende ich den Inhaltsanbieter und einen benutzerdefinierten RecyclerView-Adapter mit Cursor. In dieser Codezeile benachrichtigen Sie:

getContext().getContentResolver().notifyChange(uri, null);

Angenommen, in Ihrem recyclerView-Adapter (Schaltfläche Löschen):

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

Und in Ihrem Datenbankanbieter:

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;
MohammadL
quelle
-2

Sie sollten den Listener remove in der ViewHolder-Klasse hinzufügen

 button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                   onCancel(getAdapterPosition());

            }
        });

  private void onCancel(int position) {
        if (position >= issues.size())
            return;
        issues.remove(position);
        notifyItemRemoved(position);
    }
Kishan Vaghela
quelle