Jak zaktualizować dane adaptera RecyclerView?

274

Próbuję dowiedzieć się, na czym polega problem z aktualizacją RecyclerViewadaptera.

Po otrzymaniu nowej listy produktów próbowałem:

  1. Zaktualizuj ArrayListz fragmentu, w którym recyclerViewzostał utworzony, ustaw nowe dane na adapter, a następnie wywołaj adapter.notifyDataSetChanged(); to nie działało.

  2. Utwórz nowy adapter, tak jak inni, i działał on dla nich, ale dla mnie bez zmian: recyclerView.setAdapter(new RecyclerViewAdapter(newArrayList))

  3. Utwórz metodę, w Adapterktórej dane będą aktualizowane w następujący sposób:

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

    Następnie wywołuję tę metodę za każdym razem, gdy chcę zaktualizować listę danych; to nie działało.

  4. Aby sprawdzić, czy mogę w jakikolwiek sposób zmodyfikować recyklerView, próbowałem usunąć przynajmniej element:

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

    Wszystko pozostało bez zmian.

Oto mój adapter:

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

    }
}

I inicjuję RecyclerVieww następujący sposób:

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

Jak więc zaktualizować dane adaptera, aby wyświetlać nowo otrzymane elementy?


Aktualizacja: problem polegał na tym, że wygląd gridView wyglądał następująco:

<?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>

Potem właśnie usunąłem LinearLayouti utworzyłem FrameLayoutukład macierzysty.

Filip Luchianenco
źródło
items.clear(); items.addAll(newItems);to brzydki wzór. Jeśli naprawdę potrzebujesz tutaj defensywnego kopiowania, items = new ArrayList(newItems);byłoby mniej brzydkie.
Miha_x64,
2
@ miha-x64 Masz rację - byłoby mniej brzydkie. Problem polega na tym, że to po prostu nie działa. Adapter nie otrzymuje odniesienia do odwołania. Pobiera samą referencję. Jeśli więc zbudujesz nowy zestaw danych, ma on nowe odwołanie, podczas gdy adapter nadal zna tylko stary.
Niesamowity

Odpowiedzi:

326

Pracuję z RecyclerView i zarówno usuwanie, jak i aktualizacja działają dobrze.

1) USUŃ: Istnieją 4 kroki, aby usunąć element z RecyclerView

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

Ta linia kodów działa dla mnie.

2) AKTUALIZACJA DANYCH: Jedyne, co musiałem zrobić, to

mAdapter.notifyDataSetChanged();

Musiałeś to wszystko zrobić w kodzie Actvity / Fragment, a nie w kodzie adaptera RecyclerView.

Niccolò Passolunghi
źródło
2
Dziękuję Ci. Sprawdź aktualizację. Jakoś układ liniowy nie pozwala liście aktualizacji RecylerView.
Filip Luchianenco,
40
potrzebujesz tylko tych dwóch wierszy: list.remove (position); mAdapter.notifyItemRemoved (pozycja);
feisal
3
Dla mnie linie recycler.removeViewAt(position);(a raczej recycler.removeAllViews()) były kluczowe.
MSpeed
2
Ważna uwaga, aby uniknąć irytującej usterki podczas usuwania: Nie musisz wywoływać tej linii recyclingler.removeViewAt (pozycja);
Angelo Nodari,
7
NotifyDataSetChanged to przesada, szczególnie jeśli masz do czynienia z BARDZO długimi listami. Załaduj ponownie całość dla kilku przedmiotów? Nie, dziękuję. Ponadto, recyclingler.removeViewAt (pozycja)? Nie są potrzebne. I nie trzeba wywoływać zarówno „mAdapter.notifyItemRemoved (position)”, jak i „mAdapter.notifyItemRangeChanged (position, list.size ())”. Usuń część lub usuń jedną. Powiadom RAZ. Zadzwoń do jednego z nich.
Vitor Hugo Schwaab,
328

To ogólna odpowiedź dla przyszłych gości. Różne sposoby aktualizacji danych adaptera są wyjaśnione. Proces obejmuje dwa główne kroki za każdym razem:

  1. Zaktualizuj zestaw danych
  2. Powiadom adapter o zmianie

Wstaw pojedynczy element

Dodaj „Pig” w indeksie 2.

Wstaw pojedynczy element

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

Wstaw wiele elementów

Wstaw trzy kolejne zwierzęta według indeksu 2.

Wstaw wiele elementów

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());

Usuń pojedynczy element

Usuń „Pig” z listy.

Usuń pojedynczy element

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

Usuń wiele elementów

Usuń „Wielbłąda” i „Owcę” z listy.

Usuń wiele elementów

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

Usuń wszystkie elementy

Wyczyść całą listę.

Usuń wszystkie elementy

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

Zastąp starą listę nową

Wyczyść starą listę, a następnie dodaj nową.

Zastąp starą listę nową

// 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();

adapterMa odniesienie do data, więc ważne jest, aby nie ustawić datado nowego obiektu. Zamiast tego wyczyściłem stare elementy, dataa następnie dodałem nowe.

Zaktualizuj pojedynczy element

Zmień element „Owca”, tak aby brzmiał „Lubię owce”.

Zaktualizuj pojedynczy element

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

Przenieś pojedynczy element

Przesuń „Owcę” z pozycji 3na pozycję 1.

Przenieś pojedynczy element

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

Kod

Oto kod projektu w celach informacyjnych. Kod adaptera RecyclerView można znaleźć w tej odpowiedzi .

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

Notatki

  • Jeśli użyjesz notifyDataSetChanged(), animacja nie zostanie wykonana. Może to być również kosztowna operacja, dlatego nie zaleca się korzystania z niej, notifyDataSetChanged()jeśli aktualizujesz tylko jeden element lub szereg przedmiotów.
  • Sprawdź DiffUtil, jeśli dokonujesz dużych lub złożonych zmian na liście.

Dalsze badanie

Suragch
źródło
5
Świetna odpowiedź! Jakie byłyby kroki przy wymianie elementu wejściowego, co spowodowałoby inny typ widoku? Czy to po prostu powiadomienieItemChanged czy połączone usunięcie, a następnie wstawka?
Semafor
W minimalnym przykładzie wygląda na to, że powiadomienieItemChanged jest wystarczające. Wywoła onCreateViewHolder i onBindViewHolder dla zmienionego elementu, a pokazany zostanie inny widok. Miałem problem z aplikacją aktualizującą element, która spowodowałaby inny widok, ale wydaje się, że jest inny problem.
Semafor
@Suragch jakikolwiek pomysł na moje pytanie: stackoverflow.com/questions/57332222/ ... Próbowałem wszystkiego wymienionego w tym wątku, ale bezskutecznie :(
Co powiesz na to, kiedy korzystamy z DiffUtils do wprowadzania zmian? Nie są tak płynne jak ręczne wywoływanie .notify <Action> ().
GuilhE
1
Naprawdę upuściłem piłkę pod Update single item, powinienem być jak żółwie
nowy
46

Oto, co zadziałało dla mnie:

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

Po utworzeniu nowego adaptera zawierającego zaktualizowaną listę (w moim przypadku była to baza danych przekonwertowana na ArrayList) i ustawieniu go jako adaptera, próbowałem recyclerView.invalidate()i działało.

martinpkmn
źródło
12
czy to nie odświeży całego widoku, zamiast tylko aktualizować zmienione elementy?
TWilly,
13
Zamiast tworzyć nowy adapter za każdym razem, stworzyłem metodę ustawiającą w mojej niestandardowej klasie adaptera, aby ustawić nową listę. Po tym, po prostu zadzwoń YourAdapter adapter = (YourAdapter) recyclerView.getAdapter(); adapter.yourSetterMethod(newList); adapter.notifyDataSetChanged();To powiedziane, to brzmi jak to, co OP najpierw próbował (po prostu dodając to jako komentarz, aby mogło pomóc komuś innemu, jak to działało w moim przypadku).
Kevin Lee
2
Czytelniku, nie zadowalaj się tym. Ta odpowiedź działa, ale istnieje bardziej wydajny sposób. Zamiast ponownie ładować wszystkie dane , najbardziej wydajnym sposobem jest po prostu dodanie nowych danych do adaptera.
Sebastialonso,
Tak Zgadzam się z @TWilly przerysuje pełne Zobacz w interfejsie użytkownika.
Rahul
18

masz dwie opcje: odśwież interfejs użytkownika z adaptera:

mAdapter.notifyDataSetChanged();

lub odśwież go z recyklerViewView:

recyclerView.invalidate();
ediBersh
źródło
15

Inną opcją jest użycie diffutil . Porównuje oryginalną listę z nową listą i użyje nowej listy jako aktualizacji, jeśli nastąpi zmiana.

Zasadniczo możemy użyć DiffUtil, aby porównać stare dane z nowymi danymi i pozwolić mu na wywołanie powiadomienieIdentRangeRemoved, i powiadomienieItemRangeChanged i powiadomienieItemRangeInserted w Twoim imieniu.

Szybki przykład użycia diffUtil zamiast powiadomieniaDataSetChanged:

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)

Obliczam różnicę od głównego wątku na wypadek, gdyby była to duża lista.

j2emanue
źródło
1
chociaż jest to poprawne, w jaki sposób zaktualizować dane w karcie? Powiedzmy, że wyświetlam List <Object> w adapterze i otrzymuję nową aktualizację za pomocą List <Object>. DiffUtil obliczy różnicę i wyśle ​​ją do adaptera, ale nic nie robi z oryginalną lub nową listą (w twoim przypadku getItems(). Skąd wiesz, które dane z oryginalnej listy należy usunąć / dodać / zaktualizować?
vanomart,
1
Może ten artykuł może pomóc. .. medium.com/@nullthemall/…
j2emanue
@vanomart musisz tylko zastąpić lokalne pole listy nową wartością listy, tak jak zwykle, jedyną różnicą w tym podejściu jest to, że zamiast powiadomieniaDataSetChanged () używasz DiffUtil do aktualizacji widoku.
carrizo
jakiś pomysł na mój temat? próbowałem wszystkiego wymienionego tutaj, ale bez powodzenia: stackoverflow.com/questions/57332222/…
Dziękuję za Twoją odpowiedź! Czy możesz wyjaśnić, dlaczego powinienem umieścić „wyczyść” i „dodajWszystko” po wysyłceAktualizacje? Tnx!
Adi Azarya
10

Zaktualizuj dane widoku listy, widoku siatki i widoku recyklingu

mAdapter.notifyDataSetChanged();

lub

mAdapter.notifyItemRangeChanged(0, itemList.size());
Pankaj Talaviya
źródło
Wydaje się, że to nie aktualizuje moich danych, dopóki użytkownik nie zacznie przewijać. Rozejrzałem się po okolicy i nie mogę zrozumieć, dlaczego ...
SudoPlz,
@ SudoPlz można użyć niestandardowego detektora przewijania do wykrywania przewijania i wykonywania operacji powiadamiania, takich jak stackoverflow.com/a/31174967/4401692
Pankaj Talaviya
Dzięki, Pankaj, ale tego właśnie staram się uniknąć. Chcę zaktualizować wszystko BEZ dotykania ekranu przez użytkownika.
SudoPlz
5

Najlepszym i najfajniejszym sposobem dodawania nowych danych do danych bieżących jest

 ArrayList<String> newItems = new ArrayList<String>();
 newItems = getList();
 int oldListItemscount = alcontainerDetails.size();
 alcontainerDetails.addAll(newItems);           
 recyclerview.getAdapter().notifyItemChanged(oldListItemscount+1, al_containerDetails);
Rushi Ayyappa
źródło
2
Widzę ludzi korzystających z funkcji „replaceDataSetChanged” WSZYSTKO, czy to nie przesada? Mam na myśli, przeładuj całą listę, ponieważ kilka zmieniło się lub zostało dodanych ... Podobało mi się to podejście, wdrażane właśnie teraz za pomocą metody „replaceItemRangeInserted”.
Vitor Hugo Schwaab,
5

Ten sam problem rozwiązałem w inny sposób. Nie mam danych, na które czekam z wątku w tle, więc zacznij od listy 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();
        }

Otóż ​​to.

Ahmed Ozmaan
źródło
2

Te metody są skuteczne i dobrze zacząć korzystać z podstawowych 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();
}

Możesz zaimplementować je wewnątrz klasy adaptera lub we fragmencie lub działaniu, ale w takim przypadku musisz utworzyć instancję adaptera, aby wywołać metody powiadamiania. W moim przypadku zwykle implementuję go w adapterze.

Teocci
źródło
2

Dowiedziałem się, że naprawdę prostym sposobem na ponowne załadowanie RecyclerView jest po prostu połączenie

recyclerView.removeAllViews();

To najpierw usunie całą zawartość RecyclerView, a następnie doda ją ponownie ze zaktualizowanymi wartościami.

Jonathan Persgarden
źródło
2
Proszę nie używać tego. Będziesz prosił o kłopoty.
dell116
1

dostałem odpowiedź po długim czasie

  SELECTEDROW.add(dt);
                notifyItemInserted(position);
                SELECTEDROW.remove(position);
                notifyItemRemoved(position);
Roufal
źródło
0

Jeśli nic nie wspomniane w powyższych komentarzach działa dla Ciebie. Może to oznaczać, że problem leży gdzie indziej.

Znalazłem jedno rozwiązanie, w którym ustawiłem listę adaptera. W mojej działalności lista była zmienną instancji i zmieniałem ją bezpośrednio po zmianie danych. Ponieważ była to zmienna referencyjna, działo się coś dziwnego. Zmieniłem więc zmienną referencyjną na lokalną i użyłem innej zmiennej do aktualizacji danych, a następnie przekazałem addAll()funkcję wymienioną w powyższych odpowiedziach.

bitSmith
źródło