web-dev-qa-db-fra.com

RecyclerView se bloque lorsque "les vues supprimées ou attachées ne peuvent pas être recyclées"

J'utilise une implémentation simple de RecyclerView tirée du site Web Android en utilisant un StaggeredGridLayoutManager et je continue à avoir cette erreur qui bloque mon application: 

Java.lang.IllegalArgumentException: Scrapped or attached views may not be recycled. isScrap:false isAttached:true
            at Android.support.v7.widget.RecyclerView$Recycler.recycleViewHolderInternal(RecyclerView.Java:3501)
            at Android.support.v7.widget.RecyclerView$LayoutManager.scrapOrRecycleView(RecyclerView.Java:5355)
            at Android.support.v7.widget.RecyclerView$LayoutManager.detachAndScrapAttachedViews(RecyclerView.Java:5340)
            at Android.support.v7.widget.StaggeredGridLayoutManager.onLayoutChildren(StaggeredGridLayoutManager.Java:572)
            at Android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.Java:1918)
            at Android.support.v7.widget.RecyclerView.onLayout(RecyclerView.Java:2155)
            at Android.view.View.layout(View.Java:14008)
            at Android.view.ViewGroup.layout(ViewGroup.Java:4373)
            at Android.widget.RelativeLayout.onLayout(RelativeLayout.Java:1021)
            at Android.view.View.layout(View.Java:14008)
            at Android.view.ViewGroup.layout(ViewGroup.Java:4373)
            at Android.widget.FrameLayout.onLayout(FrameLayout.Java:448)
            at Android.view.View.layout(View.Java:14008)
            at Android.view.ViewGroup.layout(ViewGroup.Java:4373)
            at Android.widget.FrameLayout.onLayout(FrameLayout.Java:448)
            at Android.view.View.layout(View.Java:14008)
            at Android.view.ViewGroup.layout(ViewGroup.Java:4373)
            at Android.support.v7.internal.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.Java:502)
            at Android.view.View.layout(View.Java:14008)
            at Android.view.ViewGroup.layout(ViewGroup.Java:4373)
            at Android.widget.FrameLayout.onLayout(FrameLayout.Java:448)
            at Android.view.View.layout(View.Java:14008)
            at Android.view.ViewGroup.layout(ViewGroup.Java:4373)
            at Android.widget.LinearLayout.setChildFrame(LinearLayout.Java:1663)
            at Android.widget.LinearLayout.layoutVertical(LinearLayout.Java:1521)
            at Android.widget.LinearLayout.onLayout(LinearLayout.Java:1434)
            at Android.view.View.layout(View.Java:14008)
            at Android.view.ViewGroup.layout(ViewGroup.Java:4373)
            at Android.widget.FrameLayout.onLayout(FrameLayout.Java:448)
            at Android.view.View.layout(View.Java:14008)
            at Android.view.ViewGroup.layout(ViewGroup.Java:4373)
            at Android.view.ViewRootImpl.performLayout(ViewRootImpl.Java:1892)
            at Android.view.ViewRootImpl.performTraversals(ViewRootImpl.Java:1711)
            at Android.view.ViewRootImpl.doTraversal(ViewRootImpl.Java:989)
            at Android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.Java:4351)
            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:725)
            at Android.os.Handler.dispatchMessage(Handler.Java:92)
            at Android.os.Looper.loop(Looper.Java:137)
            at Android.app.ActivityThread.main(ActivityThread.Java:5041)
            at Java.lang.reflect.Method.invokeNative(Native Method)
            at Java.lang.reflect.Method.invoke(Method.Java:511)
            at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:793)
            at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:560)
            at dalvik.system.NativeStart.main(Native Method)  

Par simple, je veux dire littéralement que c'est la même implémentation tirée de cette page sur leur site web , la seule différence est que la disposition de mon élément de grille est un ImageView et un couple de TextViews, je ne vais donc pas m'inquiéter de republier mon code . 

Quelqu'un d'autre qui a cette erreur et sait comment y faire face?

94

Cette erreur est due au fait que Android:animateLayoutChanges est défini sur true dans votre code XML et que vous appelez notifyDataSetChanged() sur l'adaptateur de RecyclerView dans le code Java. 

Donc, évitez simplement d'utiliser Android:animateLayoutChanges avec RecyclerViews.

171

J'ai eu à faire face à cet accident aussi et dans mon cas, cela n'avait rien à voir avec Android:animateLayoutChanges.

La RecyclerView que nous construisions avait plus d'un type de vues et certaines avaient EditTexts. Après un certain temps, nous avons déterminé que la question était liée à la concentration. Ce bogue survient lors du recyclage de EditTexts et l’un d’eux est ciblé. 

Naturellement, nous avons essayé de supprimer le focus lorsque de nouvelles données étaient liées à une vue recyclée, mais cela ne fonctionnait pas tant que Android:focusableInTouchMode="true" n'était pas défini sur RecycleView. En réalité, c’est le seul changement nécessaire à la fin pour que cette question disparaisse.

46
Nemanja Kovacevic

J'ai supprimé la propriété Android:animateLayoutChanges de la mise en page et le problème a été résolu.

23
Özer Özcan

Parmi les raisons pour lesquelles tout le monde peut être confronté à ce problème, vérifiez si vous avez défini l'attribut Android:animateLayoutChanges="true" sur RecyclerView. Cela entraînera l'échec du recyclage et de la réinsertion des éléments de RecyclerView. Supprimez-le et attribuez l'attribut au conteneur parent de RecyclerView, tel que LinearLayout/RelativeLayout, et le problème devrait disparaître.

11
Ram Iyer

En utilisant les en-têtes collants slimfit, j'ai rencontré cette erreur. Cela était dû à la mauvaise position de la première position. J'ai eu la réponse ici

public void onBindViewHolder(MainViewHolder holder, int position) {

  final View itemView = holder.itemView;
  final LayoutManager.LayoutParams params = LayoutManager.LayoutParams.from(itemView.getLayoutParams());

  params.setSlm(LinearSLM.ID);
  params.width = ViewGroup.LayoutParams.MATCH_PARENT;
  params.setFirstPosition(item.mSectionFirstPosition);
  itemView.setLayoutParams(params);

}

assurez-vous simplement que vous transmettez la valeur correcte pour mSectionFirstPosition

8
Jaspinder Kaur

J'ai rencontré ce problème ce matin, mais je ne suis pas confronté à la même raison que celle mentionnée ci-dessus.

Via debug, j’ai trouvé que la vue d’élément de mon ViewHolder avait mParent et qu’elle n’est pas nulle, ce qui devrait normalement être nul (c’est ce que dit le journal, "la vue attachée ne peut pas être recyclée", cela signifie que si l’enfant Si la vue est déjà attachée à un parent, cela pourrait en quelque sorte causer des échecs lors du recyclage.)

Mais je n'ai pas attaché manuellement la vue enfant à chaque fois. Et j’ai trouvé que c’était fait lorsque j’essayais de gonfler la vue enfant dans mon ViewHolder, quelque chose comme:

layoutInflater.inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

Et le dernier paramètre attachToRoot devrait être faux.

Après l'avoir modifié en false, j'ai résolu mon problème.

En passant, je ne vois que cet incident s'est produit lorsque j'ai mis à niveau ma bibliothèque de support vers la dernière version 25.0.0. Avant, j'utilisais la version 23.4.0 et je ne vois pas ce problème se produire. Je suppose qu'il devrait y avoir quelque chose de changé dans la dernière bibliothèque de support.

J'espère que cette aide.

7
Anthonyeef

Dans mon cas, cela s’est produit parce que j’avais une Transition exécutée lorsque je tentais de redimensionner le RecyclerView car le clavier du logiciel était sur le point de s’afficher.

J'ai corrigé mon exclusion de RecyclerView de Transition en utilisant Transition.excludeTarget(R.id.recyclerview, true);

4
Tunji_D

Moi aussi, je recevais cette erreur chaque fois que j'avais animateLayoutChanges = "true" dans le fichier de mise en page pour RecyclerView . Supprimez cet attribut et l'erreur disparaîtra!

4
rvd

Je résous ce problème en supprimant parent.addView() dans onCreateViewHolder

C'est mon code

public MyViewHolder onCreateViewwHolder(ViewGroup parent, int viewType)  {
    Button addButton = new Button(context);
    //parent.addView(addButton);
    return new MyViewHolder(addButton);
}

Fonction à Android.support.v7.widget.RecyclerViewRecycler.recyclerViewHolderinternal() vérifier si mon bouton a déjà un parent ou non. Si nous ajoutons un bouton au parent, nous assignerons également RecyclerView à sa variable mParent.

3
egon12

J'ai résolu ce problème en appelant 

setHasStableIds(true);

dans le constructeur de l'adaptateur et en remplaçant getItemId dans l'adaptateur:

@Override
public long getItemId(int position) {
    return position;
}
2
Kilian Batzner

Alors que dans mon cas, il supprimait animateOnLayoutChange de recyclerView qui corrigeait l'incident, il me fallait tout de même pouvoir animer les modifications de présentation dans le viewHolder. Pour que cela fonctionne, la valeur LinearLayout' in the view holder needs theanimateOnLayoutChange 'est définie sur true, mais je devais ajouter notifyItemChanged à l'adaptateur. Cela a ensuite permis aux deux animations de layoutTransition de démarrer (pour développer et réduire le viewHolder) et d'éviter également l'exception supprimée. Alors oui, évitez de placer animateOnLayoutChange sur recylcerView et utilisez les différentes méthodes de notification pour activer les animations par défaut lors des changements de taille de vue.

2
kingargyle

J'ai également rencontré le même errror lors du défilement sur la RecyclerView: puis j'ai supprimé animateLayoutChanges="true" dans le fichier de présentation pour RecyclerView, puis tout a fonctionné.

2
user8796389

Dans mon cas, j’ai utilisé la TransitionManager.beginDelayedTransition() avant d’ajouter une vue au-dessus de recyclerView. J'ai enlevé le TransitionManager.beginDelayedTransition() et pas de crash.

1
Hai nguyen thanh

C'est ce qui m'est arrivé lorsque j'ai utilisé un objet personnalisé dans ViewHolder pour l'adaptateur RecyclerView

Pour résoudre le problème, j'ai effacé l'objet personnalisé qui, dans mon cas, était une minuterie dans onViewRecycled(ViewHolder holder) pour l'adaptateur, comme indiqué ci-dessous:

    public void onViewRecycled(ViewHolder holder) {
        if(holder instanceof  EntityViewHolder) {
            if(((EntityViewHolder)holder).timer != null) {
                ((EntityViewHolder) holder).timer.cancel();
            }
        }
        super.onViewRecycled(holder);
    }

Cela a corrigé le bug.

1
Androidrp

1 、 remove : supprimer les données de la liste.

2 、 notifyDataSetChanged: notifyDataSetChanged ();

3 、 notifyItemRemoved : affiche l'animation.

4 、 notifyItemRangeChanged : ajustez la taille de la vue et redessinez le viewHolders(onBindViewHolder methods)

1
ZhangTengyuan
    /**
     * Informs the recycler whether this item can be recycled. Views which are not
     * recyclable will not be reused for other items until setIsRecyclable() is
     * later set to true. Calls to setIsRecyclable() should always be paired (one
     * call to setIsRecyclabe(false) should always be matched with a later call to
     * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally
     * reference-counted.
     *
     * @param recyclable Whether this item is available to be recycled. Default value
     * is true.
     *
     * @see #isRecyclable()
     */
    public final void setIsRecyclable(boolean recyclable) {
        mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1;
        if (mIsRecyclableCount < 0) {
            mIsRecyclableCount = 0;
            if (DEBUG) {
                throw new RuntimeException("isRecyclable decremented below 0: " +
                        "unmatched pair of setIsRecyable() calls for " + this);
            }
            Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: " +
                    "unmatched pair of setIsRecyable() calls for " + this);
        } else if (!recyclable && mIsRecyclableCount == 1) {
            mFlags |= FLAG_NOT_RECYCLABLE;
        } else if (recyclable && mIsRecyclableCount == 0) {
            mFl`enter code here`ags &= ~FLAG_NOT_RECYCLABLE;
        }
        if (DEBUG) {
            Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this);
        }
    }
1
ZhangTengyuan

cette exception n'est pas cause de 

Android: animateLayoutChanges

ou

Android: focusableInTouchMode

cette réponse correcte finale est juste parce que vous définissez un WRONG LayoutParams .

    nameLP = new LinearLayout.LayoutParams(context.getResources().getDisplayMetrics().widthPixels, LinearLayout.LayoutParams.WRAP_CONTENT);
    nameLP2 = new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT);

le nomLP est OK . le nomLP2 se produit le crash .bug est ici. 

J'essaie toutes les réponses de cette page. croyez-moi.

1
evin

Il existe plusieurs raisons pour lesquelles cette exception est appelée. Dans mon cas, cela était dû aux animations exécutées, c'est pourquoi les vues sont toujours attachées et ne peuvent pas être supprimées de la vue. Une fois l'animation terminée, la vue peut être supprimée et recyclée.

Il existe deux types d'animation pouvant affecter le recyclage de recyclerview.

1) Est-ce que le RecyclerView.ItemAnimator - cela ne devrait pas être le problème. Cela devrait être assez sûr à utiliser car il vérifie les vues attachées et mises au rebut et gère correctement le recyclage.

2) Android:animateLayoutChanges="true" ou TransitionManager.beginDelayedTransition() ou TransitionManager.go (), etc. - Ces animations s'exécutent seules et prennent en charge les éléments à animer. Ce résultat sur les vues étant forcé d'être attaché jusqu'à la fin de l'animation. Recyclerview n'a aucune connaissance de ces animations car elles sortent de son cadre. Par conséquent, recyclerview peut essayer de recycler un élément en pensant qu'il peut être recyclé correctement, mais le problème est que ces API conservent les vues jusqu'à la fin de l'animation.

Si vous utilisez Android:animateLayoutChanges="true" ou TransitionManager.beginDelayedTransition() ou TransitionManager.go (), etc., supprimez simplement la variable RecyclerView et ses enfants de l'animation.

Vous pouvez le faire simplement en prenant la Transition et en appelant

Transition.excludeChildren(yourRecyclerView, true)
Transition.excludeTarget(yourRecyclerView, true)

Remarque:

Notez qu'il est important d'utiliser Transition.excludeChildren() pour exclure tous les enfants Recyclerview de l'animation et pas seulement la Recyclerview elle-même.

0

J'ai eu ce problème parce que j'écrase les méthodes equals() et hashcode() de ViewHolder de RecyclerView. ViewHolder en calculant l'égalité des données et le hashcode, la logique de recyclage ne fonctionnait pas et se bloquait, je supprime simplement l'écrasement et le corrige.

0
Irwin

Un cas particulier qui s'est produit pour moi est que j'avais un membre de la vue dans l'adaptateur et que j'étais paresseux pour créer une vue qu'il n'est pas nécessaire de faire avec la vue de recyclage.

Cela va également à l’encontre des principes de recyclage des vues qui vous permettent de stocker une référence à la vue dans ce cas. Je donne un exemple rapide ci-dessous:

// typically we would do this in a grid view adapter:
View v;
// ...
if(v = null){
v = LayoutInflater.inflate ...;
}

// Now with recycle view there is NO need to store a reference to View
// and lazy instantiate. So get rid of your View v member
0
Jonny2Plates

Solution de contournement si la raison de l'exception est ce que itemView a parent. Dans le code, où vous avez notifyItemRemoved (position), supprimez itemView de RecyclerView:

View itemView = mRecyclerView.getLayoutManager().findViewByPosition(position);
if (itemView != null && itemView.getParent() != null) {
    ((ViewGroup) itemView.getParent()).removeView(itemView);
}
notifyItemRemoved(position);
0
Polurival

J'utilise com.squareup.picasso.RequestCreator 

public void into(Android.widget.ImageView target,
             Callback callback)

redimensionner dynamiquement la taille d'ImageView après le téléchargement d'une image à partir d'Internet et enregistre la largeur et la hauteur redimensionnées pour conserver la taille de la vue. J'ai eu cette exception parce que j'ai enregistré LayoutParams dans une Map et que dans mon onBindViewHolder, je l'ai récupérée et directement définie à ma ImageView. Je résous ce problème en utilisant ImmutablePair<Integer, Integer> pour ne stocker que la taille de ImageView plutôt que de nombreux autres états, et utilise le code suivant pour le restaurer.

ViewGroup.LayoutParams params = image.getLayoutParams();
params.width = widthAndHeight.getLeft();
params.height = widthAndHeight.getRight();
image.setLayoutParams(params);
0
cmicat

Permettez-moi d'ajouter une autre solution possible à ce type de problème, s'il vous plaît. J'ai eu le même problème avec superSlim bibliothèque pour les en-têtes collants dans RecyclerView. J'ai utilisé MatrixCursor pour définir les données sur RecyclerViewCursorAdapter. La raison de ce problème était que les colonnes d'ID étaient égales à 0 pour tous les en-têtes. J'espère que cela aiderait quelqu'un à économiser quelques jours de débogage.

0
MistaGreen

Dans mon cas, le problème était dû à une implémentation incorrecte de cette méthode public long getItemId(int position) (remplacée par la méthode RecyclerView.Adapter). 

L'ancien code aura deux identifiants différents pour le même élément (dans mon cas, il s'agit de l'élément de bas de page).

0
Mu Sa

Supprimez Android:animateLayoutChanges="true" de recycleview ou définissez Android:animateLayoutChanges="false"

0
Rajesh Nasit

Pour moi, le même bug causé par LayoutTransition sur un ViewGroup de niveau supérieur.

0
mattlaabs