Comment mettre à jour les données de l'adaptateur RecyclerView?

275

Essayer de comprendre quel est le problème avec la mise à jour RecyclerViewde l'adaptateur.

Après avoir reçu une nouvelle liste de produits, j'ai essayé de:

  1. Mettez à jour le à ArrayListpartir du fragment où recyclerViewest créé, définissez de nouvelles données sur l'adaptateur, puis appelez adapter.notifyDataSetChanged(); cela n'a pas fonctionné.

  2. Créez un nouvel adaptateur, comme d'autres l'ont fait, et cela a fonctionné pour eux, mais aucun changement pour moi: recyclerView.setAdapter(new RecyclerViewAdapter(newArrayList))

  3. Créez une méthode dans Adapterlaquelle met à jour les données comme suit:

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

    Ensuite, j'appelle cette méthode chaque fois que je veux mettre à jour la liste des données; cela n'a pas fonctionné.

  4. Pour vérifier si je peux modifier le recyclerView de quelque manière que ce soit, et j'ai essayé de supprimer au moins un élément:

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

    Tout est resté tel quel.

Voici mon adaptateur:

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

    }
}

Et j'initie RecyclerViewcomme suit:

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

Alors, comment puis-je réellement mettre à jour les données de l'adaptateur afin d'afficher les éléments nouvellement reçus?


Mise à jour: le problème était que la disposition dans laquelle gridView était présentée était la suivante:

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

Ensuite, je viens de supprimer LinearLayoutet FrameLayoutde créer la disposition parent.

Filip Luchianenco
la source
items.clear(); items.addAll(newItems);est un motif laid. Si vous avez vraiment besoin d'une copie défensive ici, ce items = new ArrayList(newItems);serait moins moche.
Miha_x64
2
@ miha-x64 Vous avez raison - ce serait moins moche. Le problème est que cela ne fonctionne tout simplement pas. L'adaptateur ne reçoit aucune référence à la référence. Il obtient la référence elle-même. Donc, si vous créez un nouvel ensemble de données, il a une nouvelle référence alors que l'adaptateur ne connaît que l'ancien.
L'incroyable

Réponses:

326

Je travaille avec RecyclerView et la suppression et la mise à jour fonctionnent bien.

1) SUPPRIMER: Il y a 4 étapes pour supprimer un élément d'un RecyclerView

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

Cette ligne de codes fonctionne pour moi.

2) METTRE À JOUR LES DONNÉES: La seule chose que je devais faire est

mAdapter.notifyDataSetChanged();

Vous avez dû faire tout cela dans le code Actvity / Fragment et non dans le code de l'adaptateur RecyclerView.

Niccolò Passolunghi
la source
2
Je vous remercie. Veuillez vérifier la mise à jour. D'une manière ou d'une autre, la disposition linéaire ne laisse pas la liste de mise à jour RecylerView.
Filip Luchianenco
40
vous n'avez besoin que de ces deux lignes: list.remove (position); mAdapter.notifyItemRemoved (position);
2016
3
Pour moi, les lignes recycler.removeViewAt(position);(ou plutôt recycler.removeAllViews()) étaient cruciales.
MSpeed
2
Une considération importante pour éviter un problème gênant lors de la suppression: vous n'avez pas besoin d'appeler cette ligne recycler.removeViewAt (position);
Angelo Nodari
7
NotifyDataSetChanged est exagéré, surtout si vous avez affaire à de TRÈS longues listes. Recharger le tout pour quelques articles? Non merci. Aussi, recycler.removeViewAt (position)? Pas besoin. Et vous n'avez pas besoin d'appeler à la fois "mAdapter.notifyItemRemoved (position)" et "mAdapter.notifyItemRangeChanged (position, list.size ())". Supprimez-en ou supprimez-en un. Avertissez UNE FOIS. Appelez l'un d'eux.
Vitor Hugo Schwaab
329

Ceci est une réponse générale pour les futurs visiteurs. Les différentes façons de mettre à jour les données de l'adaptateur sont expliquées. Le processus comprend à chaque fois deux étapes principales:

  1. Mettre à jour l'ensemble de données
  2. Avertissez l'adaptateur de la modification

Insérer un seul élément

Ajoutez "Pig" à l'index 2.

Insérer un seul élément

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

Insérer plusieurs éléments

Insérez trois autres animaux à l'index 2.

Insérer plusieurs éléments

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

Supprimer un seul élément

Supprimer "Pig" de la liste.

Supprimer un seul élément

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

Supprimer plusieurs éléments

Supprimer "Chameau" et "Mouton" de la liste.

Supprimer plusieurs éléments

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

Supprimer tous les éléments

Effacez toute la liste.

Supprimer tous les éléments

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

Remplacer l'ancienne liste par une nouvelle liste

Effacez l'ancienne liste puis ajoutez-en une nouvelle.

Remplacer l'ancienne liste par une nouvelle liste

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

Le adaptera une référence à data, il est donc important que je n'aie pas défini datade nouvel objet. Au lieu de cela, j'ai effacé les anciens éléments data, puis ajouté les nouveaux.

Mettre à jour un seul élément

Modifiez l'élément "Mouton" pour qu'il indique "J'aime les moutons".

Mettre à jour un seul élément

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

Déplacer un seul élément

Déplacez le "mouton" d'une position 3à l'autre 1.

Déplacer un seul élément

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

Code

Voici le code du projet pour votre référence. Le code de l'adaptateur RecyclerView se trouve à cette réponse .

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

Remarques

  • Si vous utilisez notifyDataSetChanged(), aucune animation ne sera effectuée. Cela peut également être une opération coûteuse, il n'est donc pas recommandé de l'utiliser notifyDataSetChanged()si vous ne mettez à jour qu'un seul élément ou une plage d'éléments.
  • Consultez DiffUtil si vous apportez des modifications importantes ou complexes à une liste.

Une étude plus approfondie

Suragch
la source
5
Réponse géniale! Quelles seraient les étapes lors du remplacement d'un élément dans, ce qui entraînerait un type de vue différent? S'agit-il simplement de notifyItemChanged ou d'une suppression combinée suivie d'une insertion?
Semaphor
Avec votre exemple minimal, il semble que notifyItemChanged soit suffisant. Il appellera onCreateViewHolder et onBindViewHolder pour l'élément modifié et la vue différente est affichée. J'ai eu un problème dans une application mettant à jour un élément qui entraînerait une vue différente, mais il semble y avoir un autre problème.
Semaphor
@Suragch une idée de ma question: stackoverflow.com/questions/57332222/… J'ai essayé tout ce qui est répertorié dans ce fil, mais en vain :(
Qu'en est-il lorsque nous utilisons DiffUtils pour apporter des modifications? Ils ne sont pas aussi fluides que d'appeler manuellement .notify <Action> ().
GuilhE
1
Vraiment laissé tomber le ballon sous Update single item, aurait dû être j'aime les tortues
ardnew
46

C'est ce qui a fonctionné pour moi:

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

Après avoir créé un nouvel adaptateur qui contient la liste mise à jour (dans mon cas, c'était une base de données convertie en ArrayList) et l'avoir définie comme adaptateur, j'ai essayé recyclerView.invalidate()et cela a fonctionné.

martinpkmn
la source
12
cela ne rafraîchirait-il pas la vue entière au lieu de simplement mettre à jour les éléments qui ont changé?
TWilly
13
Au lieu de créer un nouvel adaptateur à chaque fois, j'ai créé une méthode de définition dans ma classe d'adaptateurs personnalisés pour définir la nouvelle liste. Après cela, appelez simplement YourAdapter adapter = (YourAdapter) recyclerView.getAdapter(); adapter.yourSetterMethod(newList); adapter.notifyDataSetChanged();Cela étant dit, cela ressemble à ce que le PO a essayé en premier (en ajoutant simplement cela en tant que commentaire afin qu'il puisse aider quelqu'un d'autre, comme cela a fonctionné dans mon cas).
Kevin Lee
2
Lecteur, ne vous contentez pas de cela. Cette réponse fonctionne, mais il existe un moyen plus efficace. Au lieu de recharger à nouveau toutes les données , le moyen le plus efficace consiste simplement à ajouter les nouvelles données à l'adaptateur.
Sebastialonso
Oui, je suis d'accord avec @TWilly, il redessinera la vue complète sur l'interface utilisateur.
Rahul
18

vous avez 2 options pour ce faire: actualiser l'interface utilisateur à partir de l'adaptateur:

mAdapter.notifyDataSetChanged();

ou actualisez-le à partir de recyclerView lui-même:

recyclerView.invalidate();
ediBersh
la source
15

Une autre option consiste à utiliser diffutil . Il comparera la liste d'origine à la nouvelle liste et utilisera la nouvelle liste comme mise à jour en cas de changement.

Fondamentalement, nous pouvons utiliser DiffUtil pour comparer les anciennes données aux nouvelles données et le laisser appeler notifierItemRangeRemoved, notifierItemRangeChanged et notifierItemRangeInsert en votre nom.

Un exemple rapide d'utilisation de diffUtil au lieu de 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)

Je fais le calcul de Diff sur le thread principal au cas où ce serait une grande liste.

j2emanue
la source
1
bien que cela soit correct, comment mettre à jour les données à l'intérieur de votre adaptateur? Disons que j'affiche List <Object> dans l'adaptateur et que j'obtiens une nouvelle mise à jour avec List <Object>. DiffUtil calcule une différence et la distribue dans l'adaptateur, mais il ne fait rien avec la liste d'origine ou la nouvelle (dans votre cas getItems(). Comment savez-vous quelles données de la liste d'origine doivent être supprimées / ajoutées / mises à jour?
vanomart
1
Peut-être que cet article peut vous aider. .. medium.com/@nullthemall/…
j2emanue
@vanomart vous n'avez qu'à remplacer directement votre champ de liste local par la nouvelle valeur de liste comme vous le feriez normalement, la seule différence dans cette approche est qu'au lieu de notifyDataSetChanged () vous utilisez DiffUtil pour mettre à jour la vue.
carrizo
une idée sur la mienne? j'ai essayé tout ce qui est répertorié ici mais pas de chance: stackoverflow.com/questions/57332222/…
Merci pour votre réponse! Pouvez-vous s'il vous plaît expliquer pourquoi je devrais mettre le "clear" & "addAll" après le dispatchUpdatesTo ?? Tnx!
Adi Azarya
10

Mettre à jour les données de listview, gridview et recyclerview

mAdapter.notifyDataSetChanged();

ou

mAdapter.notifyItemRangeChanged(0, itemList.size());
Pankaj Talaviya
la source
Cela ne semble pas mettre à jour mes données jusqu'à ce que l'utilisateur commence à faire défiler. J'ai regardé partout et je n'arrive pas à comprendre pourquoi ..
SudoPlz
@SudoPlz vous pouvez utiliser l'écouteur de défilement personnalisé pour détecter le défilement et effectuer votre opération de notification comme stackoverflow.com/a/31174967/4401692
Pankaj Talaviya
Merci Pankaj mais c'est exactement ce que j'essaye d'éviter. Je veux tout mettre à jour SANS que l'utilisateur touche l'écran.
SudoPlz
5

Le meilleur et le plus cool moyen d'ajouter de nouvelles données aux données actuelles est

 ArrayList<String> newItems = new ArrayList<String>();
 newItems = getList();
 int oldListItemscount = alcontainerDetails.size();
 alcontainerDetails.addAll(newItems);           
 recyclerview.getAdapter().notifyItemChanged(oldListItemscount+1, al_containerDetails);
Rushi Ayyappa
la source
2
Je vois des gens utiliser le "notifyDataSetChanged" pour TOUT, n'est-ce pas exagéré? Je veux dire, recharger toute la liste parce que quelques-uns ont changé ou ont été ajoutés ... J'aime cette approche, qui était en train d'être implémentée en ce moment, avec la méthode "notifyItemRangeInsert".
Vitor Hugo Schwaab
5

J'ai résolu le même problème d'une manière différente. Je n'ai pas de données que j'attends du fil d'arrière-plan, alors commencez par une liste 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();
        }

C'est tout.

Ahmed Ozmaan
la source
2

Ces méthodes sont efficaces et bonnes pour commencer à utiliser une 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();
}

Vous pouvez les implémenter à l'intérieur d'une classe d'adaptateur ou dans votre fragment ou activité, mais dans ce cas, vous devez instancier l'adaptateur pour appeler les méthodes de notification. Dans mon cas, je l'implémente généralement dans l'adaptateur.

Teocci
la source
2

J'ai découvert qu'un moyen très simple de recharger le RecyclerView est d'appeler

recyclerView.removeAllViews();

Cela supprimera d'abord tout le contenu de RecyclerView, puis l'ajoutera à nouveau avec les valeurs mises à jour.

Jonathan Persgarden
la source
2
Veuillez ne pas l'utiliser. Vous demanderez des ennuis.
dell116
1

j'ai la réponse après un long moment

  SELECTEDROW.add(dt);
                notifyItemInserted(position);
                SELECTEDROW.remove(position);
                notifyItemRemoved(position);
roufal
la source
0

Si rien mentionné dans les commentaires ci-dessus ne fonctionne pour vous. Cela pourrait signifier que le problème se situe ailleurs.

Un endroit où j'ai trouvé la solution était dans la façon dont je définissais la liste sur l'adaptateur. Dans mon activité, la liste était une variable d'instance et je la modifiais directement lorsque des données changeaient. Comme il s'agissait d'une variable de référence, il se passait quelque chose de bizarre. J'ai donc changé la variable de référence en une variable locale et utilisé une autre variable pour mettre à jour les données, puis passer à la addAll()fonction mentionnée dans les réponses ci-dessus.

bitSmith
la source