web-dev-qa-db-fra.com

Mises à jour des données de RecyclerView et de l'adaptateur

Il s'agit d'une question sur le comportement interne de RecyclerView pour quelqu'un qui connaît ses mécanismes ou qui souhaite creuser dans le code source. Je voudrais une réponse appuyée par des références à la source.

Question d'origine

(faites défiler jusqu'à ‘En d’autres termes’ pour une question plus ciblée)

J'ai besoin de comprendre comment les actions notify* (Par exemple, notifyItemInserted()) sont mises en file d'attente. Imaginez que j'ai un adaptateur sauvegardé par cette liste:

ArrayList<String> list = Arrays.asList("one", "three", "four");

Je veux ajouter les valeurs zero et two, qui manquent.

Exemple 1

list.add(1, "two");
// notify the view
adapter.notifyItemInserted(1);


// Seconds later, I go on with zero
list.add(0, "zero");
// notify the view
adapter.notifyItemInserted(0);

C'est assez simple et clair, rien à dire.

Exemple 2

Mais que se passe-t-il si les deux actions sont très proches l'une de l'autre et qu'aucune disposition ne passe entre les deux?

list.add(1, "two");
list.add(0, "zero”);

Que dois-je faire maintenant?

adapter.notifyItemInserted(1);
adapter.notifyItemInserted(0);

Ou peut-être

adapter.notifyItemInserted(2);
adapter.notifyItemInserted(0);

? Du point de vue de l'adaptateur, la liste est immédiatement passée de one, three, four À zero, one, two, three, four, De sorte que la deuxième option semble plus raisonnable.

Exemple 3

list.add(0, “zero”);
adapter.notifyItemInserted(0);
list.add(2, “two”);
adapter.notifyItemInserted(...)

Et maintenant? 1 Ou 2? La liste a été mise à jour immédiatement après, mais je suis sûr qu'il n'y a eu aucune passe de mise en page entre les deux.

Question

Vous avez le problème principal, et je veux savoir comment dois-je me comporter dans ces situations. Le vrai cas est que j'ai plusieurs tâches asynchrones se terminant par une méthode insert(). Je peux mettre leurs opérations en file d'attente, mais:

  1. Je ne veux pas faire ça s'il y a déjà une file d'attente interne, et il y a sûrement
  2. Je ne sais pas ce qui se passe si deux actions se produisent sans une passe de mise en page entre les deux, voir l'exemple 3.

En d'autres termes

Pour mettre à jour le recycleur, 4 actions doivent se produire:

  1. Je modifie en fait le modèle de données (par exemple, insérer quelque chose dans le tableau de support)
  2. J'appelle adapter.notify*()
  3. Le recycleur reçoit l'appel
  4. Recycler exécute l'action (par exemple, appelle getItem*() et onBind() sur l'adaptateur) et définit la modification.

Il est facile de comprendre cela lorsqu'il n'y a pas de simultanéité, et cela se produit dans l'ordre:

1. => 2. => 3. => 4. => (new update) 1. => 2. => 3. => 4. ...

Voyons ce qui se passe entre les étapes.

  • Entre 1. et 2.: Je dirais que c'est la responsabilité du développeur d'appeler notify () immédiatement après avoir modifié les données. C'est bon.
  • Entre 2. et .: Cela se produit immédiatement, aucun problème ici.
  • Entre . et 4.: Cela ne pas se produit immédiatement! AUTANT QUE JE SACHE. Il est donc parfaitement possible qu'une nouvelle mise à jour (étapes 1 et 2) intervienne entre les étapes et 4 - de la mise à jour précédente.

Je veux comprendre ce qui se passe dans ce cas. Comment devons-nous nous comporter? Dois-je m'assurer que l'étape 4 de la mise à jour précédente a bien eu lieu avant d'insérer de nouveaux éléments? Si c'est le cas, comment?

22
natario

J'ai déjà pensé à des questions similaires et j'ai décidé:

  1. Si je veux insérer plus d'un élément directement à la fin de la liste et que je souhaite obtenir une animation pour tous, je dois:

    list.add("0");
    list.add("1");
    adapter.notifyItemRangeInserted(5, 2); // Suppose there were 5 items before so "0" has index of 5 and we want to insert 2 items.
    
  2. Si je veux insérer plus d'un élément directement à la fin de la liste, mais que je souhaite obtenir une animation séparée pour chaque élément inséré, je dois:

    list.add("0");
    list.add("1");
    adapter.notifyItemInserted(0);
    mRecyclerView.postDelayed(new Runnable() {
        @Override
        public void run() {
            // before this happens, Be careful to call other notify* methods. Never call notifyDataSetChanged.
            adapter.notifyItemInserted(1); 
        }
    }, mRecyclerView.getItemAnimator().getAddDuration());
    
  3. Si je veux insérer plus d'un élément à une position différente de la liste, similaire à 2.

J'espère que cela peut vous aider.

7
ywwynm

Permet donc de commencer d'une petite introduction à RecyclerView fonctionne avec les éléments de notification. Et fonctionne assez simple avec d'autres listes d'éléments ViewGroup enregistrés (ListView par exemple)

RecyclerView a la file d'attente des éléments de vue déjà dessinés. Et ne connaît pas vos mises à jour, sans appeler les méthodes notify(...). Lorsque vous avez ajouté de nouveaux éléments et avertissez RecyclerView, il démarre le cycle de vérification de toutes les vues une par une.

RecyclerView contains and drawn next objects
View view-0 (position 0), view-1 (position 1), View-2 (position 2)

// Here is changes after updating
You added Item View view-new into (position 1) and Notify
RecyclerView starts loop to check changes
RecyclerView received unmodified view-0(position-0) and left them;
RecyclerView found new item view-new(position 1)
RecyclerView removing old item view-1(position 1)
RecyclerView drawing new item view-new(position 1)

// In RecyclerView queue in position-2 was item view-2, 
// But now we replacing previous item to this position
RecyclerView found new item view-1 (new position-2)
RecyclerView removing old item view-2(position 2)
RecyclerView drawing new item view-1(position 2)

// And again same behavior 
RecyclerView found new item view-3 (new position-3)
RecyclerView drawing new item view-1(position 2)

// And after all changes new RecyclerView would be
RecyclerView contains and drawn next objects
View view-0 (position 0), view-new (position 1) view-1 (position 2), View-2 (position 3)

C'est juste le flux principal des fonctions de notification de travail, mais ce qui devrait savoir que toutes ces actions se produisent sur le thread d'interface utilisateur, le thread principal, même vous pouvez appeler la mise à jour à partir de tâches asynchrones. Et pour vous répondre 2 Question - Vous pouvez appeler Notify au RecyclerView autant que vous le souhaitez, et assurez-vous que votre action se trouve dans la file d'attente correcte.

RecyclerView fonctionne correctement dans n'importe quelle utilisation, des questions plus compliquées seraient liées au travail de votre adaptateur. Tout d'abord, vous devez synchroniser votre action d'adaptateur, comme ajouter des éléments de suppression, et refuser totalement l'utilisation de l'index. Par exemple, ce serait mieux pour votre exemple 3

Item firstItem = new Item(0, “zero”);
list.add(firstItem);
adapter.notifyItemInserted(list.indexOf(firstItem));
//Other action...
Item nextItem = new Item(2, “two”);
list.add(nextItem);
adapter.notifyItemInserted(list.indexOf(nextItem))
//Other actions

MISE À JOUR |

Lié à RecyclerView.Adapter Doc , où vous pouvez voir les mêmes fonctions avec notifyDataSetChanged(). Et lorsque ce RecyclerView.Adapter Invoque des éléments enfants avec des extensions Android.database.Observable, Voir plus À propos d'Observable . L'accès à ce support observable est synchronisé jusqu'à l'utilisation de View Element dans RecyclerView.

Voir aussi RecyclerView à partir de la bibliothèque de support version 25.0, lignes 9934 - 9988;

4
GensaGames

Cela ne devrait pas être un problème si vous effectuez plusieurs mises à jour entre les passes de mise en page. RecyclerView est conçu pour gérer (et optimiser) ce cas:

RecyclerView introduit un niveau d'abstraction supplémentaire entre RecyclerView.Adapter et RecyclerView.LayoutManager pour pouvoir détecter les modifications de l'ensemble de données dans les lots lors d'un calcul de disposition. [...] Il existe deux types de méthodes liées à la position dans RecyclerView:

  • position de mise en page: position d'un élément dans le dernier calcul de mise en page. Il s'agit de la position du point de vue du LayoutManager.
  • position de l'adaptateur: position d'un élément dans l'adaptateur. C'est la position du point de vue de l'adaptateur.

Ces deux positions sont identiques sauf le temps entre la distribution des événements adapter.notify * et le calcul de la mise à jour .

Dans votre cas, les étapes sont les suivantes:

  1. Vous mettez à jour la couche de données

  2. Vous appelez adapter.notify*()

  3. La vue de recyclage enregistre la modification (dans AdapterHelper.mPendingUpdates si je comprends bien le code). Cette modification sera reflétée dans ViewHolder.getAdapterPosition () , mais pas encore dans ViewHolder.getLayoutPosition () .

  4. À un moment donné, le recyclerView applique les modifications enregistrées, fondamentalement, il réconcilie le point de vue de la mise en page avec le point de vue de l'adaptateur. Il semble que cela puisse se produire avant le passage de la mise en page.

Le 1. , 2. , 3. la séquence peut se produire autant de fois que 2. suit immédiatement 1. (et les deux se produisent sur le thread principal).

(1. => 2. => 3.) ... (1. => 2. => 3.) ... 4. 
2
bwt