RecyclerView se bloque lorsque "les vues mises au rebut ou jointes ne peuvent pas être recyclées"


115

J'utilise une implémentation simple de RecyclerViewtirée du site Web Android en utilisant un StaggeredGridLayoutManageret je continue à recevoir cette erreur qui plante 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 ImageViewet quelques TextViews, donc je ne prendrai pas la peine de republier mon code.

Quelqu'un d'autre reçoit cette erreur et sait comment y remédier?


Vous avez une solution?
Pratik Butani

Réponses:


191

Cette erreur se produit si dans votre XML vous avez android:animateLayoutChangesdéfini sur true et que vous appeleznotifyDataSetChanged() l'adaptateur de RecyclerView dans le code Java.

Alors, évitez simplement d'utiliser android:animateLayoutChangesavec RecyclerViews.


22
alors comment on peut utiliser la fonctionnalité animateLayoutChanges dans recyclerview?
dhuma1981

Qu'essayez-vous de réaliser? Animation d'article? Si tel est le cas, l'API RecyclerView prend en charge cela - jetez un œil à la documentation: developer.android.com/reference/android/support/v7/widget/...
Kenneth

4
@ dhuma1981 si l'animateur d'élément est défini via mRecyclerView.setItemAnimator (new DefaultItemAnimator ()); alors animateLayoutChanges n'a pas besoin d'être vrai
Rich Ehmer

RecyclerViewutilise DefaultItemAnimatorpar défaut.
Benjamin

-, - J'ai ce problème exactement comme vous l'avez décrit
Ninja Coding

52

J'ai dû faire face à ce crash aussi et dans mon cas, cela n'avait rien à voir avec android:animateLayoutChanges.

Le que RecyclerViewnous construisions avait plus d'un type de vues et certains avaient des vues EditTexten eux. Après un certain temps, nous avons épinglé le problème comme étant lié à la concentration. Ce bug se produit lors du recyclage des EditTexts et l'un d'eux est ciblé.

Naturellement, nous avons essayé de dégager le focus lorsque de nouvelles données sont liées à une vue recyclée, mais cela n'a pas fonctionné avant d' android:focusableInTouchMode="true"être activé RecycleView. En fait, c'est le seul changement qui était nécessaire à la fin pour que ce problème disparaisse.


2
Fantastique, résolu plusieurs problèmes liés à la mise au point que j'avais lors de l'utilisation d'EditTexts dans un RecyclerView. Merci!
Rabie Jradi

1
J'ai eu ACET dans recyclerview, et il écrase. Ce message m'a sauvé.
Kai Wang

Et je n'ai pas d'edittexts dans les éléments, j'ai des cases à cocher cependant. devrais-je essayer android:focusableInTouchMode="true"parce que cela n'arrive que parfois dans certains appareils (rarement) et je suppose que cela ne concerne pas mon problème, mais stack-trace pour un crash est presque le même.
Shivansh

C'était mon cas, mais le réglage ne android:focusableInTouchMode="true"m'a pas aidé du tout. J'ai donc effacé le focus dans le onViewDetachedFromWindowrappel. public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { if (holder instanceof ItemViewHolder) { ItemViewHolder item = (ItemViewHolder) holder; if (item.editText.isFocused()) item.editText.clearFocus(); } }
artman

24

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


J'ai commencé à avoir ce crash lorsque j'ai également placé le android:animateLayoutChangessur mon VR.
Mauker

2
Cet indicateur avait-il été défini sur le conteneur parent (Disposition relative). Correction du problème.
1911z

@ 1911z êtes-vous en train de dire que vous aviez le drapeau sur le conteneur parent et que le supprimer de là a résolu le problème?
RamPrasadBismil

14

Parmi les raisons pour lesquelles n'importe qui peut faire face à 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 remise en place des éléments du RecyclerView. Supprimez-le et affectez l'attribut au conteneur parent du RecyclerView, tel qu'un LinearLayout / RelativeLayout et vous devriez voir le problème disparaître.


J'ai vu ce crash même lorsque j'ai défini l'attribut sur le conteneur parent du RV.
RamPrasadBismil

@RamPrasadBismil Veuillez poster votre code et peut-être que nous pourrons l'examiner?
Ram Iyer le

12

Cela m'a pris deux jours mais je n'ai pas pu contourner cela, à la fin, j'ai dû désactiver la prélecture de l'élément.

Lors de la configuration du gestionnaire de mise en page, vous pouvez simplement appeler

mGridLayoutManager.setItemPrefetchEnabled(false);

Cela a fait disparaître l'erreur pour moi. J'espère que cela sera utile pour quelqu'un.


A travaillé pour moi. Merci.
Vicky

1
Je crains vraiment que les gens adoptent cette solution. Vous perdez beaucoup en désactivant ce drapeau et le bogue est toujours ailleurs -
Felipe Castilhos

8

Lors de l'utilisation d'en-têtes collants slimfit, j'ai rencontré cette erreur. Cela a été causé par une mauvaise première position. J'ai 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 passez la valeur correcte pour mSectionFirstPosition


Bienvenue dans StackOverflow. Pourriez-vous s'il vous plaît fournir une réponse complète au lieu d'un simple lien?
slfan du

qu'y a- itemt-il ici?
Bugs Happen

C'est l'élément de liste qui doit être affiché dans la vue du recycleur. Donc, fondamentalement, je sauvegarde la première position de la section contre chaque élément de la liste.
Jaspinder Kaur

8

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 le débogage, j'ai trouvé que la vue des éléments dans mon ViewHolder mParent et qu'elle n'est pas nulle, ce qui, dans le cas normal, ne devrait pas être nulle (c'est ce que dit le journal, "la vue attachée ne peut pas être recyclée", je pense que cela signifie que si la vue enfant est déjà attaché à un parent, cela causerait un échec lors du recyclage.)

Mais je n'ai pas attaché la vue enfant à chaque fois manuellement. 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 doit être faux.

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

À propos, je ne vois que ce plantage s'est produit lorsque je mets à 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.


8

J'ai également rencontré la même erreur lors du défilement sur le RecyclerView: puis j'ai supprimé le animateLayoutChanges="true"fichier de mise en page pour que RecyclerViewtout fonctionne.


6

Dans mon cas, cela s'est produit parce que j'avais une Transitioncourse en essayant de redimensionner le RecyclerView parce que le clavier logiciel était sur le point de s'afficher.

Je l'ai corrigé en excluant RecyclerView de la Transitionen utilisant Transition.excludeTarget(R.id.recyclerview, true);


6

Il y a un certain nombre de raisons pour lesquelles cette exception est appelée. Dans mon cas, cela était dû à des animations en cours d'exécution, c'est pourquoi les vues sont toujours attachées et n'ont pas pu être supprimées de la vue. Ce n'est que lorsque l'animation est terminée que la vue peut être supprimée et recyclée.

Il existe deux types d'animation qui pourraient affecter le recyclage de la vue de recyclage.

1) Est-ce que le RecyclerView.ItemAnimator -ce que - cela ne devrait pas être le problème. Cela devrait être à peu près 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 toutes seules et saisissent les éléments à animer. Il en résulte que les vues doivent être attachées jusqu'à ce que l'animation soit terminée. Les recyclerview n'ont aucune connaissance de ces animations car elles sont hors de son champ d'application. Par conséquent, ils recyclerviewpourraient essayer de recycler un élément en pensant qu'il pourrait être recyclé correctement, mais le problème est que ces API conservent les vues jusqu'à ce que l'animation soit terminée.

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

Vous pouvez simplement le faire en saisissant le Transitionet en appelant

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

Remarque:

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


Merci! TransitionManager.beginDelayedTransition () était la cause du problème dans mon cas. Vous pouvez mettre à jour votre exemple de code avec plus de détails sur la façon de l'utiliser Transition.excludeChildren. Vous instanciez un objet de transition comme:, val transition = AutoTransition()appelez excludeChildren(recyclerView, true)cet objet et passez-le à beginDelayedTransaction() as the second parameter.
Danilo Prado le

5

J'obtenais moi aussi 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!


Veuillez noter que cette question date de 2014 et que les propriétés peuvent avoir changé maintenant.
Korashen

2
Non, ce n'est pas encore changé
Sanjay Kushwah

4

Alors que dans mon cas, c'était la suppression animateOnLayoutChangede recyclerView qui corrigeait le plantage, j'avais toujours besoin de la possibilité d'animer les modifications de mise en page dans le viewHolder. Pour que cela fonctionne, l' LinearLayout' in the view holder needs theanimateOnLayoutChange 'sur true, mais j'avais besoin notifyItemChangedde l'adaptateur. Cela a ensuite permis aux deux animations layoutTransition de démarrer (pour développer et réduire le viewHolder), et évite également l'exception abandonnée. Alors oui, évitez de mettre l'animateOnLayoutChange sur le recylcerView et utilisez les différentes méthodes de notification pour activer les animations par défaut lors des changements de taille de vue.


J'essaie de faire la même chose pour animer l'expansion de l'élément, mais appeler notifyItemChanged dans l'adaptateur fait clignoter l'élément après le changement (l'animation fonctionne)! Comment évitez-vous cela?
Flyview

Pour notre cas d'utilisation, nous avons fini par échanger notre code personnalisé en utilisant le animateOnLayoutChange, pour utiliser une mise en page extensible. Il accomplit la même chose que nous essayions d'accomplir mais beaucoup plus flexible. github.com/chuross/expandable-layout
kingargyle

3

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

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. Qui si nous ajoutons un bouton au parent, il sera également assigné RecyclerViewà sa mParentvariable.


Je pense que c'est une nouvelle exigence, je l'ai attaché au parent dans la version 24 du support de la bibliothèque, une fois mis à jour à 25 j'ai eu le plantage.
Kirill Kulakov

1

J'ai vu cela se produire pour moi lorsque j'ai utilisé un objet personnalisé dans ViewHolderleRecyclerView adaptateur.

Pour résoudre le problème, j'ai effacé l'objet personnalisé qui, dans mon cas, était une minuterie dans onViewRecycled(ViewHolder holder)l'adaptateur comme 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 bogue.


1
    /**
     * 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

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

2 、notifyDataSetChanged: notifyDataSetChanged ();

3 、notifyItemRemoved: montrer l'animation.

4 、notifyItemRangeChanged: la taille de la vue et redessinez laviewHolders(onBindViewHolder methods)


J'ai fait notifyItemRemovedlors de la suppression footeret des plantages d'application, changez-le en notifyDataSetChangedet maintenant cela fonctionne bien. merci
Siarhei

1

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


1

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

setHasStableIds(true);

dans le constructeur de l'adaptateur et le remplacement getItemIddans l'adaptateur:

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

1

Supprimer android:animateLayoutChanges="true"de la recycleview ou définirandroid:animateLayoutChanges="false"


0

j'utilise com.squareup.picasso.RequestCreator

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

pour redimensionner dynamiquement la taille d'ImageView après le téléchargement de l'image à partir d'Internet, et enregistre la largeur et la hauteur redimensionnées pour préserver la taille de la vue. J'ai obtenu cette exception parce que j'ai enregistré LayoutParamsdans un Map, et dans mon onBindViewHolder, je l'ai récupéré et l'ai directement défini sur mon ImageView. Je corrige cela en utilisant ImmutablePair<Integer, Integer>uniquement la taille d'ImageView plutôt que de nombreux autres états, et j'utilise le code suivant pour le restaurer.

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

0

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


0

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


0

Dans mon cas, le problème était dû à la mise en œuvre incorrecte de cette méthode public long getItemId(int position)(remplacé parRecyclerView.Adapter méthode).

L'ancien code obtiendra deux identifiants différents pour le même élément (dans mon cas, il s'agit de l'élément de pied de page), après avoir corrigé l'implémentation, le problème a disparu.


0

Solution de contournement si la raison de l'exception est ce que itemView a un 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

Un cas particulier qui s'est produit pour moi était que j'avais un membre de vue dans l'adaptateur et que j'étais paresseux instanciant 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, dans ce cas, vous permettent de stocker une référence à la vue. 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

cette exception n'est pas cause de

android: animateLayoutChanges

ou

Android: focusableInTouchMode

cette réponse correcte finale est simplement parce que vous avez défini un mauvais 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'essaye toutes les réponses de cette page. croyez-moi.


0

J'ai eu ce problème parce que j'Ecraser equals()et hashcode()méthode ViewHolderde RecyclerView.ViewHolder en calculant l' égalité des données et hashcode, alors la logique de recyclage ne marchait pas et écrasé, je retire tout l'écrasement et fixe.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.