web-dev-qa-db-fra.com

RecyclerView.Adapter.notifyItemChanged () ne transmet jamais la charge à onBindViewHolder ()

J'essaie de mettre à jour une ViewHolder dans une RecyclerView sans réaffecter le tout. Selon la documentation, je devrais le faire en appelant RecyclerView.Adapter.notifyItemChanged(int position, Object payload), où payload est un objet arbitraire qui sera passé à RecyclerView.Adapter.onBindViewHolder(VH holder, int position, List<Object> payloads), où je pourrai mettre à jour le ViewHolder.

Mais lorsque j'essaie, onBindViewHolder reçoit toujours une liste vide. Entre ces deux appels, la liste interne des données utiles est effacée. Après avoir défini des points d'arrêt dans le code source de RecyclerView, cela est dû à une sortie de relais qui appelle finalement RecyclerView.ViewHolder.clearPayload().

Est-ce que quelqu'un d'autre a réussi à faire fonctionner ça? Est-ce un bug dans la bibliothèque de support ou quelque chose que j'ai déjà fait qui déclenche un relais entre ces deux fonctions?

Voici la trace de la pile lorsque le payload est effacé:

"<1> main@831692616832" prio=5 runnable
  Java.lang.Thread.State: RUNNABLE
      at Android.support.v7.widget.RecyclerView$ViewHolder.clearPayload(RecyclerView.Java:8524)
      at Android.support.v7.widget.RecyclerView$ViewHolder.resetInternal(RecyclerView.Java:8553)
      at Android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.Java:4544)
      at Android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.Java:4461)
      at Android.support.v7.widget.LayoutState.next(LayoutState.Java:86)
      at Android.support.v7.widget.StaggeredGridLayoutManager.fill(StaggeredGridLayoutManager.Java:1423)
      at Android.support.v7.widget.StaggeredGridLayoutManager.onLayoutChildren(StaggeredGridLayoutManager.Java:610)
      at Android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.Java:2847)
      at Android.support.v7.widget.RecyclerView.onLayout(RecyclerView.Java:3145)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.Java:581)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.widget.FrameLayout.onLayout(FrameLayout.Java:448)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.widget.FrameLayout.onLayout(FrameLayout.Java:448)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.widget.FrameLayout.onLayout(FrameLayout.Java:448)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.widget.FrameLayout.onLayout(FrameLayout.Java:448)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.widget.LinearLayout.setChildFrame(LinearLayout.Java:1671)
      at Android.widget.LinearLayout.layoutVertical(LinearLayout.Java:1525)
      at Android.widget.LinearLayout.onLayout(LinearLayout.Java:1434)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.support.v4.widget.DrawerLayout.onLayout(DrawerLayout.Java:1043)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.widget.FrameLayout.onLayout(FrameLayout.Java:448)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.widget.LinearLayout.setChildFrame(LinearLayout.Java:1671)
      at Android.widget.LinearLayout.layoutVertical(LinearLayout.Java:1525)
      at Android.widget.LinearLayout.onLayout(LinearLayout.Java:1434)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.widget.FrameLayout.onLayout(FrameLayout.Java:448)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.widget.LinearLayout.setChildFrame(LinearLayout.Java:1671)
      at Android.widget.LinearLayout.layoutVertical(LinearLayout.Java:1525)
      at Android.widget.LinearLayout.onLayout(LinearLayout.Java:1434)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.widget.FrameLayout.onLayout(FrameLayout.Java:448)
      at Android.view.View.layout(View.Java:14289)
      at Android.view.ViewGroup.layout(ViewGroup.Java:4562)
      at Android.view.ViewRootImpl.performLayout(ViewRootImpl.Java:1976)
      at Android.view.ViewRootImpl.performTraversals(ViewRootImpl.Java:1730)
      at Android.view.ViewRootImpl.doTraversal(ViewRootImpl.Java:1004)
      at Android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.Java:5481)
      at Android.view.Choreographer$CallbackRecord.run(Choreographer.Java:749)
      at Android.view.Choreographer.doCallbacks(Choreographer.Java:562)
      at Android.view.Choreographer.doFrame(Choreographer.Java:532)
      at Android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.Java:735)
      at Android.os.Handler.handleCallback(Handler.Java:730)
      at Android.os.Handler.dispatchMessage(Handler.Java:92)
      at Android.os.Looper.loop(Looper.Java:137)
      at Android.app.ActivityThread.main(ActivityThread.Java:5103)
      at Java.lang.reflect.Method.invokeNative(Method.Java:-1)
      at Java.lang.reflect.Method.invoke(Method.Java:525)
      at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:737)
      at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:553)
      at dalvik.system.NativeStart.main(NativeStart.Java:-1)
23
philippe

RecyclerView par défaut crée une autre copie de la ViewHolder afin de faire apparaître en fondu les vues. Cela cause le problème parce que l’ancien ViewHolder récupère la charge utile mais pas le nouveau. Vous devez donc explicitement lui dire de réutiliser l'ancien:

DefaultItemAnimator animator = new DefaultItemAnimator() {
        @Override
        public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
            return true;
        }
    };
mRecyclerView.setItemAnimator(animator);
29
Patricia Li

Si vous utilisez l'élément animator (activé par défaut), les données utiles seront toujours vides. Regardez cette réponse dans Android Issue Tracker :

C'est ainsi que la charge utile est conçue pour le travail. Il n'est transmis à on bind "que si nous sommes en liaison avec le même détenteur de vue". Idem pour la liaison de données, si vous ne liez pas au même détenteur de vue, vous ne pouvez pas faire la liaison de manière incrémentielle. En cas d'animation de modification, RV crée deux copies de la vue à fondre afin que vous ne soyez pas lié au même VH dans postLayout.

Bientôt, nous fournirons une API pour exécuter des animations de changement dans le même ViewHolder.

Par conséquent, jusqu'à ce qu'ils partagent cette nouvelle API, nous devons utiliser quelque chose comme ceci:

mRecyclerView.setItemAnimator(null);

edit: Voir le commentaire @blindOSX, où il a remarqué que pour activer les charges utiles, vous ne pouvez désactiver que les animations d'événements de changement d'élément.

edit2: On dirait qu'ils ont mis à jour ce comportement sans préavis. Voir la mise à jour @Patricia Li answer .

12
sosite

Dans certains cas, cela fonctionnait avec notifyItemRangeChanged(getItemRealCount(), 1).

1
joe

pourquoi ne pas simplement utiliser RecyclerView.Adapter.notifyItemChanged(int position) la documentation semble un peu ambigu tout en mentionnant 

  RecyclerView.Adapter.notifyItemChanged(int position,Object payload)

Le client peut éventuellement transmettre une charge utile pour un changement partiel. Celles-ci les données utiles seront fusionnées et peuvent être passées à l'adaptateur onBindViewHolder (ViewHolder, int, List)

par conséquent, il n'est pas garanti que vous receviez la liste dans votre onBindViewHolder 

0
harshitpthk