Come aggiornare i dati dell'adattatore RecyclerView?


275

Sto cercando di capire qual è il problema con l'aggiornamento RecyclerViewdell'adattatore.

Dopo aver ricevuto un nuovo elenco di prodotti, ho provato a:

  1. Aggiorna il ArrayListdal frammento in cui recyclerViewè stato creato, imposta i nuovi dati sull'adattatore, quindi chiama adapter.notifyDataSetChanged(); non ha funzionato.

  2. Crea un nuovo adattatore, come hanno fatto gli altri, e ha funzionato per loro, ma nessuna modifica per me: recyclerView.setAdapter(new RecyclerViewAdapter(newArrayList))

  3. Creare un metodo in Adaptercui aggiorni i dati come segue:

    public void updateData(ArrayList<ViewModel> viewModels) {
       items.clear();
       items.addAll(viewModels);
       notifyDataSetChanged();
    }

    Quindi chiamo questo metodo ogni volta che voglio aggiornare l'elenco dei dati; non ha funzionato.

  4. Per verificare se posso modificare recyclerView in qualsiasi modo e ho provato a rimuovere almeno un elemento:

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

    Tutto è rimasto com'era.

Ecco il mio adattatore:

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> implements View.OnClickListener {

    private ArrayList<ViewModel> items;
    private OnItemClickListener onItemClickListener;

    public RecyclerViewAdapter(ArrayList<ViewModel> items) {
        this.items = items;
    }


    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler, parent, false);
        v.setOnClickListener(this);
        return new ViewHolder(v);
    }

    public void updateData(ArrayList<ViewModel> viewModels) {
        items.clear();
        items.addAll(viewModels);
        notifyDataSetChanged();
    }
    public void addItem(int position, ViewModel viewModel) {
        items.add(position, viewModel);
        notifyItemInserted(position);
    }

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



    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        ViewModel item = items.get(position);
        holder.title.setText(item.getTitle());
        Picasso.with(holder.image.getContext()).load(item.getImage()).into(holder.image);
        holder.price.setText(item.getPrice());
        holder.credit.setText(item.getCredit());
        holder.description.setText(item.getDescription());

        holder.itemView.setTag(item);
    }

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

    @Override
    public void onClick(final View v) {
        // Give some time to the ripple to finish the effect
        if (onItemClickListener != null) {
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    onItemClickListener.onItemClick(v, (ViewModel) v.getTag());
                }
            }, 0);
        }
    }

    protected static class ViewHolder extends RecyclerView.ViewHolder {
        public ImageView image;
        public TextView price, credit, title, description;

        public ViewHolder(View itemView) {
            super(itemView);
            image = (ImageView) itemView.findViewById(R.id.image);
            price = (TextView) itemView.findViewById(R.id.price);
            credit = (TextView) itemView.findViewById(R.id.credit);
            title = (TextView) itemView.findViewById(R.id.title);
            description = (TextView) itemView.findViewById(R.id.description);
        }
    }

    public interface OnItemClickListener {

        void onItemClick(View view, ViewModel viewModel);

    }
}

E inizio RecyclerViewcome segue:

recyclerView = (RecyclerView) view.findViewById(R.id.recycler);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 5));
adapter = new RecyclerViewAdapter(items);
adapter.setOnItemClickListener(this);
recyclerView.setAdapter(adapter);

Quindi, come posso effettivamente aggiornare i dati dell'adattatore per visualizzare gli articoli appena ricevuti?


Aggiornamento: il problema era che il layout in cui gridView era simile al seguente:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:tag="catalog_fragment"
    android:layout_height="match_parent">

    <FrameLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false"/>

        <ImageButton
            android:id="@+id/fab"
            android:layout_gravity="top|end"
            style="@style/FabStyle"/>

    </FrameLayout>
</LinearLayout>

Quindi ho appena rimosso LinearLayoute creato FrameLayoutcome layout principale.


items.clear(); items.addAll(newItems);è un modello brutto. Se hai davvero bisogno di una copia difensiva qui, items = new ArrayList(newItems);sarebbe meno brutto.
Miha_x64,

2
@ miha-x64 Hai ragione - sarebbe meno brutto. Il problema è che questo non funziona. L'adattatore non ottiene alcun riferimento al riferimento. Ottiene il riferimento stesso. Pertanto, se si crea un nuovo set di dati, questo ha un nuovo riferimento mentre l'adattatore conosce ancora solo quello vecchio.
L'incredibile gennaio

Risposte:


326

Sto lavorando con RecyclerView e sia la rimozione che l'aggiornamento funzionano bene.

1) RIMUOVI: ci sono 4 passaggi per rimuovere un oggetto da RecyclerView

list.remove(position);
recycler.removeViewAt(position);
mAdapter.notifyItemRemoved(position);                 
mAdapter.notifyItemRangeChanged(position, list.size());

Questa linea di codici funziona per me.

2) AGGIORNARE I DATI: L'unica cosa che dovevo fare è

mAdapter.notifyDataSetChanged();

Hai dovuto fare tutto questo nel codice Actvity / Fragment non nel codice dell'adattatore RecyclerView.


2
Grazie. Si prega di verificare l'aggiornamento. In qualche modo, il layout lineare non consente all'elenco di aggiornamento di RecylerView.
Filip Luchianenco,

40
hai solo bisogno di queste due righe: list.remove (posizione); mAdapter.notifyItemRemoved (posizione);
feisal

3
Per me, le linee recycler.removeViewAt(position);(o meglio recycler.removeAllViews()) erano quelle cruciali.
MSpeed

2
Una considerazione importante per evitare fastidiosi problemi durante l'eliminazione: non è necessario chiamare questa linea recycler.removeViewAt (posizione);
Angelo Nodari,

7
NotifyDataSetChanged è eccessivo, specialmente se hai a che fare con elenchi MOLTO lunghi. Ricarica tutto per alcuni oggetti? No grazie. Inoltre, recycler.removeViewAt (posizione)? Non necessario. E non è necessario chiamare sia "mAdapter.notifyItemRemoved (position)" e "mAdapter.notifyItemRangeChanged (position, list.size ())". Rimuovine o rimuovine una. Notifica UNA VOLTA. Chiama uno di loro.
Vitor Hugo Schwaab,

329

Questa è una risposta generale per i futuri visitatori. Vengono spiegati i vari modi per aggiornare i dati dell'adattatore. Il processo prevede due passaggi principali ogni volta:

  1. Aggiorna il set di dati
  2. Notifica l'adattatore della modifica

Inserisci un singolo articolo

Aggiungi "Maiale" all'indice 2.

Inserisci un singolo articolo

String item = "Pig";
int insertIndex = 2;
data.add(insertIndex, item);
adapter.notifyItemInserted(insertIndex);

Inserisci più elementi

Inserisci altri tre animali all'indice 2.

Inserisci più elementi

ArrayList<String> items = new ArrayList<>();
items.add("Pig");
items.add("Chicken");
items.add("Dog");
int insertIndex = 2;
data.addAll(insertIndex, items);
adapter.notifyItemRangeInserted(insertIndex, items.size());

Rimuovi elemento singolo

Rimuovere "Pig" dall'elenco.

Rimuovi elemento singolo

int removeIndex = 2;
data.remove(removeIndex);
adapter.notifyItemRemoved(removeIndex);

Rimuovi più elementi

Rimuovi "Cammello" e "Pecora" dall'elenco.

Rimuovi più elementi

int startIndex = 2; // inclusive
int endIndex = 4;   // exclusive
int count = endIndex - startIndex; // 2 items will be removed
data.subList(startIndex, endIndex).clear();
adapter.notifyItemRangeRemoved(startIndex, count);

Rimuovi tutti gli elementi

Cancella l'intero elenco.

Rimuovi tutti gli elementi

data.clear();
adapter.notifyDataSetChanged();

Sostituisci vecchio elenco con nuovo elenco

Cancella il vecchio elenco, quindi aggiungine uno nuovo.

Sostituisci vecchio elenco con nuovo elenco

// clear old list
data.clear();

// add new list
ArrayList<String> newList = new ArrayList<>();
newList.add("Lion");
newList.add("Wolf");
newList.add("Bear");
data.addAll(newList);

// notify adapter
adapter.notifyDataSetChanged();

Il adapterha un riferimento data, per cui è importante che non ho impostato dataa un nuovo oggetto. Invece ho cancellato i vecchi oggetti datae poi ho aggiunto quelli nuovi.

Aggiorna singolo elemento

Modifica l'elemento "Pecora" in modo che sia scritto "Mi piacciono le pecore".

Aggiorna singolo elemento

String newValue = "I like sheep.";
int updateIndex = 3;
data.set(updateIndex, newValue);
adapter.notifyItemChanged(updateIndex);

Sposta elemento singolo

Sposta "Pecora" da una posizione 3all'altra 1.

Sposta elemento singolo

int fromPosition = 3;
int toPosition = 1;

// update data array
String item = data.get(fromPosition);
data.remove(fromPosition);
data.add(toPosition, item);

// notify adapter
adapter.notifyItemMoved(fromPosition, toPosition);

Codice

Ecco il codice del progetto come riferimento. Il codice dell'adattatore RecyclerView è disponibile a questa risposta .

MainActivity.java

public class MainActivity extends AppCompatActivity implements MyRecyclerViewAdapter.ItemClickListener {

    List<String> data;
    MyRecyclerViewAdapter adapter;

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

        // data to populate the RecyclerView with
        data = new ArrayList<>();
        data.add("Horse");
        data.add("Cow");
        data.add("Camel");
        data.add("Sheep");
        data.add("Goat");

        // set up the RecyclerView
        RecyclerView recyclerView = findViewById(R.id.rvAnimals);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
                layoutManager.getOrientation());
        recyclerView.addItemDecoration(dividerItemDecoration);
        adapter = new MyRecyclerViewAdapter(this, data);
        adapter.setClickListener(this);
        recyclerView.setAdapter(adapter);
    }

    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(this, "You clicked " + adapter.getItem(position) + " on row number " + position, Toast.LENGTH_SHORT).show();
    }

    public void onButtonClick(View view) {
        insertSingleItem();
    }

    private void insertSingleItem() {
        String item = "Pig";
        int insertIndex = 2;
        data.add(insertIndex, item);
        adapter.notifyItemInserted(insertIndex);
    }

    private void insertMultipleItems() {
        ArrayList<String> items = new ArrayList<>();
        items.add("Pig");
        items.add("Chicken");
        items.add("Dog");
        int insertIndex = 2;
        data.addAll(insertIndex, items);
        adapter.notifyItemRangeInserted(insertIndex, items.size());
    }

    private void removeSingleItem() {
        int removeIndex = 2;
        data.remove(removeIndex);
        adapter.notifyItemRemoved(removeIndex);
    }

    private void removeMultipleItems() {
        int startIndex = 2; // inclusive
        int endIndex = 4;   // exclusive
        int count = endIndex - startIndex; // 2 items will be removed
        data.subList(startIndex, endIndex).clear();
        adapter.notifyItemRangeRemoved(startIndex, count);
    }

    private void removeAllItems() {
        data.clear();
        adapter.notifyDataSetChanged();
    }

    private void replaceOldListWithNewList() {
        // clear old list
        data.clear();

        // add new list
        ArrayList<String> newList = new ArrayList<>();
        newList.add("Lion");
        newList.add("Wolf");
        newList.add("Bear");
        data.addAll(newList);

        // notify adapter
        adapter.notifyDataSetChanged();
    }

    private void updateSingleItem() {
        String newValue = "I like sheep.";
        int updateIndex = 3;
        data.set(updateIndex, newValue);
        adapter.notifyItemChanged(updateIndex);
    }

    private void moveSingleItem() {
        int fromPosition = 3;
        int toPosition = 1;

        // update data array
        String item = data.get(fromPosition);
        data.remove(fromPosition);
        data.add(toPosition, item);

        // notify adapter
        adapter.notifyItemMoved(fromPosition, toPosition);
    }
}

Appunti

  • Se si utilizza notifyDataSetChanged(), non verrà eseguita alcuna animazione. Questa può anche essere un'operazione costosa, quindi non è consigliabile utilizzarla notifyDataSetChanged()se si aggiorna solo un singolo articolo o un intervallo di articoli.
  • Dai un'occhiata a DiffUtil se stai apportando modifiche grandi o complesse a un elenco.

Ulteriore studio


5
Risposta fantastica! Quali sarebbero i passaggi quando si sostituisce un elemento in, che comporterebbe un tipo di vista diverso? È solo notificationItemChanged o una rimozione combinata seguita da un inserimento?
Semafora,

Con il tuo esempio minimo, sembra che notificationItemChanged sia sufficiente. Chiamerà onCreateViewHolder e onBindViewHolder per l'elemento modificato e verrà mostrata la diversa vista. Ho riscontrato un problema in un'applicazione durante l'aggiornamento di un elemento che avrebbe comportato una visualizzazione diversa, ma sembra che ci sia un altro problema.
Semafora

@Suragch qualsiasi idea sulla mia domanda: stackoverflow.com/questions/57332222/… Ho provato tutto ciò che è elencato in questa discussione ma inutilmente :(

Che ne dici di usare DiffUtils per apportare modifiche? Non sono così fluidi come chiamare manualmente .notify <Azione> ().
GuilhE

1
Ho davvero lasciato cadere la palla sotto Update single item, avrei dovuto essere come le tartarughe
Ardnew

46

Questo è ciò che ha funzionato per me:

recyclerView.setAdapter(new RecyclerViewAdapter(newList));
recyclerView.invalidate();

Dopo aver creato un nuovo adattatore che contiene l'elenco aggiornato (nel mio caso era un database convertito in un ArrayList) e averlo impostato come adattatore, ho provato recyclerView.invalidate()e ha funzionato.


12
questo non aggiorna l'intera vista invece di aggiornare semplicemente gli elementi che sono stati modificati?
Twilly,

13
Invece di creare un nuovo adattatore ogni volta, quello che ho fatto è stato creare un metodo setter nella mia classe dell'adattatore personalizzato per impostare il nuovo elenco. Dopodiché, basta chiamare YourAdapter adapter = (YourAdapter) recyclerView.getAdapter(); adapter.yourSetterMethod(newList); adapter.notifyDataSetChanged();Detto questo, suona come quello che l'OP ha provato per primo (aggiungendo questo come commento in modo che possa aiutare qualcun altro, come ha funzionato nel mio caso).
Kevin Lee,

2
Lettore, non accontentarti di questo. Questa risposta funziona, ma esiste un modo più efficiente. Invece di ricaricare nuovamente tutti i dati , il modo più efficiente è semplicemente aggiungere i nuovi dati all'adattatore.
Sebastialonso,

Sì, sono d'accordo con @TWilly ridisegnerà la visualizzazione completa sull'interfaccia utente.
Rahul,

18

hai 2 opzioni per fare questo: aggiorna l'interfaccia utente dall'adattatore:

mAdapter.notifyDataSetChanged();

o aggiornalo da recyclerView stesso:

recyclerView.invalidate();

15

Un'altra opzione è usare diffutil . Confronterà l'elenco originale con il nuovo elenco e utilizzerà il nuovo elenco come aggiornamento se si verifica una modifica.

Fondamentalmente, possiamo usare DiffUtil per confrontare i vecchi dati con i nuovi dati e lasciarlo chiamare notificationItemRangeRemoved e notifyItemRangeChanged e notifyItemRangeInserted per tuo conto.

Un rapido esempio di utilizzo di diffUtil invece di notifyDataSetChanged:

DiffResult diffResult = DiffUtil
                .calculateDiff(new MyDiffUtilCB(getItems(), items));

//any clear up on memory here and then
diffResult.dispatchUpdatesTo(this);

//and then, if necessary
items.clear()
items.addAll(newItems)

Faccio calcola il lavoro del thread principale nel caso in cui sia una grande lista.


1
sebbene sia corretto, come si aggiornano i dati all'interno dell'adattatore? Diciamo che sto visualizzando Elenco <oggetto> nell'adattatore e ricevo un nuovo aggiornamento con Elenco <oggetto>. DiffUtil calcolerà una differenza e la spedirà nell'adattatore, ma non sta facendo nulla con l'elenco originale o nuovo (nel tuo caso getItems(). Come fai a sapere quali dati dell'elenco originale devono essere rimossi / aggiunti / aggiornati?
vanomart

1
Forse questo articolo può aiutare. .. medium.com/@nullthemall/…
j2emanue,

@vanomart devi solo sostituire direttamente il campo della tua lista locale con il nuovo valore della lista come faresti normalmente, l'unica differenza in questo approccio è che invece di notificationDataSetChanged () usi DiffUtil per aggiornare la vista.
Carrizo

qualche idea sulla mia? Ho provato di tutto elencati qui, ma senza fortuna: stackoverflow.com/questions/57332222/...

La ringrazio per la risposta! Puoi spiegare perché dovrei mettere "clear" e "addAll" dopo il dispatchUpdatesTo ?? Tnx!
Adi Azarya,

10

Aggiorna i dati di listview, gridview e recyclerview

mAdapter.notifyDataSetChanged();

o

mAdapter.notifyItemRangeChanged(0, itemList.size());

Questo non sembra aggiornare i miei dati fino a quando l'utente non inizia a scorrere. Ho guardato dappertutto e non riesco a capire perché ...
SudoPlz

@SudoPlz è possibile utilizzare ascoltatore scorrimento personalizzato per rilevare scorrimento ed eseguire l'operazione notificare come stackoverflow.com/a/31174967/4401692
Pankaj Talaviya

Grazie Pankaj ma è esattamente quello che sto cercando di evitare. Voglio aggiornare tutto SENZA che l'utente tocchi lo schermo.
SudoPlz,

5

Il modo migliore e più interessante per aggiungere nuovi dati ai dati attuali è

 ArrayList<String> newItems = new ArrayList<String>();
 newItems = getList();
 int oldListItemscount = alcontainerDetails.size();
 alcontainerDetails.addAll(newItems);           
 recyclerview.getAdapter().notifyItemChanged(oldListItemscount+1, al_containerDetails);

2
Vedo persone che usano "notifyDataSetChanged" per TUTTO, non è eccessivo? Voglio dire, ricaricare l'intero elenco perché alcuni sono stati modificati o aggiunti ... Mi piace questo approccio, si stava implementando proprio ora, con il metodo "notifyItemRangeInserted".
Vitor Hugo Schwaab,

5

Ho risolto lo stesso problema in modo diverso. Non ho dati che li aspetto dal thread in background, quindi inizia con un elenco emty.

        mAdapter = new ModelAdapter(getContext(),new ArrayList<Model>());
   // then when i get data

        mAdapter.update(response.getModelList());
  //  and update is in my adapter

        public void update(ArrayList<Model> modelList){
            adapterModelList.clear(); 
            for (Product model: modelList) {
                adapterModelList.add(model); 
            }
           mAdapter.notifyDataSetChanged();
        }

Questo è tutto.


2

Questi metodi sono efficienti e utili per iniziare a utilizzare un metodo di base RecyclerView.

private List<YourItem> items;

public void setItems(List<YourItem> newItems)
{
    clearItems();
    addItems(newItems);
}

public void addItem(YourItem item, int position)
{
    if (position > items.size()) return;

    items.add(item);
    notifyItemInserted(position);
}

public void addMoreItems(List<YourItem> newItems)
{
    int position = items.size() + 1;
    newItems.addAll(newItems);
    notifyItemChanged(position, newItems);
}

public void addItems(List<YourItem> newItems)
{
    items.addAll(newItems);
    notifyDataSetChanged();
}

public void clearItems()
{
    items.clear();
    notifyDataSetChanged();
}

public void addLoader()
{
    items.add(null);
    notifyItemInserted(items.size() - 1);
}

public void removeLoader()
{
    items.remove(items.size() - 1);
    notifyItemRemoved(items.size());
}

public void removeItem(int position)
{
    if (position >= items.size()) return;

    items.remove(position);
    notifyItemRemoved(position);
}

public void swapItems(int positionA, int positionB)
{
    if (positionA > items.size()) return;
    if (positionB > items.size()) return;

    YourItem firstItem = items.get(positionA);

    videoList.set(positionA, items.get(positionB));
    videoList.set(positionB, firstItem);

    notifyDataSetChanged();
}

Puoi implementarli all'interno di una classe Adapter o nel tuo frammento o attività, ma in tal caso devi istanziare l'adapter per chiamare i metodi di notifica. Nel mio caso di solito lo implemento nell'adattatore.


2

Ho scoperto che un modo davvero semplice per ricaricare RecyclerView è semplicemente chiamare

recyclerView.removeAllViews();

Questo rimuoverà prima tutto il contenuto di RecyclerView e poi lo aggiungerà di nuovo con i valori aggiornati.


2
Per favore non usare questo. Chiederai problemi.
dell116,

1

ho ricevuto la risposta dopo tanto tempo

  SELECTEDROW.add(dt);
                notifyItemInserted(position);
                SELECTEDROW.remove(position);
                notifyItemRemoved(position);

0

Se nulla di menzionato nei commenti sopra funziona per te. Potrebbe significare che il problema sta altrove.

Un posto in cui ho trovato la soluzione era il modo in cui stavo impostando l'elenco sull'adattatore. Nella mia attività la lista era una variabile di istanza e la stavo cambiando direttamente quando cambiarono i dati. Essendo una variabile di riferimento, stava succedendo qualcosa di strano. Quindi ho cambiato la variabile di riferimento in una locale e ho usato un'altra variabile per aggiornare i dati e quindi passare alla addAll()funzione menzionata nelle risposte sopra.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.