RecyclerView si arresta in modo anomalo quando "le viste scartate o allegate potrebbero non essere riciclate"


115

Sto utilizzando una semplice implementazione di RecyclerViewpresa dal sito Web Android utilizzando a StaggeredGridLayoutManagere continuo a ricevere questo errore che blocca la mia app:

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)  

Con semplice, intendo letteralmente che è la stessa implementazione presa da questa pagina sul loro sito Web , l'unica differenza è che il layout del mio elemento della griglia è un ImageViewe un paio di TextViews, quindi non mi preoccuperò di ripubblicare il mio codice.

Qualcun altro riceve questo errore e sa come affrontarlo?


Hai qualche soluzione?
Pratik Butani

Risposte:


191

Questo errore è causato se nel tuo XML hai android:animateLayoutChangesimpostato su true e chiami notifyDataSetChanged()l'adattatore di RecyclerView nel codice Java.

Quindi, evita di usarlo android:animateLayoutChangescon RecyclerViews.


22
allora come si può utilizzare la funzione animateLayoutChanges in recyclerview?
dhuma1981

Cosa stai cercando di ottenere? Animazione dell'oggetto? In tal caso, l'API RecyclerView lo supporta: dai un'occhiata alla documentazione: developer.android.com/reference/android/support/v7/widget/…
Kenneth

4
@ dhuma1981 se l'animatore dell'elemento è impostato tramite mRecyclerView.setItemAnimator (new DefaultItemAnimator ()); quindi animateLayoutChanges non deve essere vero
Rich Ehmer

RecyclerViewutilizza DefaultItemAnimatorper impostazione predefinita.
Benjamin

-, - Ho questo problema esattamente come l'hai descritto
Codifica Ninja

52

Ho dovuto affrontare anche questo incidente e nel mio caso non ha avuto nulla a che fare con android:animateLayoutChanges.

L' RecyclerViewedificio che stavamo costruendo aveva più di un tipo di vista e alcune ne avevano EditText. Dopo un po 'abbiamo bloccato il problema in modo che fosse correlato al focus. Questo bug si verifica durante il riciclaggio di se EditTextuno di essi è concentrato.

Naturalmente abbiamo provato a cancellare il focus quando i nuovi dati vengono associati a una visualizzazione riciclata, ma ciò non ha funzionato fino a quando non android:focusableInTouchMode="true"è stato impostato RecycleView. In realtà questo è l'unico cambiamento che è stato necessario alla fine per risolvere questo problema.


2
Fantastico, risolto più problemi relativi alla messa a fuoco che ho avuto durante l'utilizzo di EditTexts in un RecyclerView. Grazie!
Rabie Jradi

1
Ho avuto ACET in recyclerview, e si schiaccia. Questo post mi ha salvato.
Kai Wang

E non ho testi di modifica negli elementi, ho comunque delle caselle di controllo. dovrei provare android:focusableInTouchMode="true"perché succede solo a volte in alcuni dispositivi (raramente) e immagino che non sia correlato al mio problema, ma stack-trace per crash è quasi lo stesso.
Shivansh

Questo era il mio caso, ma l'impostazione non android:focusableInTouchMode="true"mi ha aiutato affatto. Quindi ho cancellato il focus nella onViewDetachedFromWindowrichiamata. public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { if (holder instanceof ItemViewHolder) { ItemViewHolder item = (ItemViewHolder) holder; if (item.editText.isFocused()) item.editText.clearFocus(); } }
artman

24

Ho rimosso la android:animateLayoutChangesproprietà dal layout e il problema è stato risolto.


Ho iniziato a subire questo incidente quando ho posizionato anche il android:animateLayoutChangessul mio camper.
Mauker

2
Aveva questo flag impostato sul contenitore padre (layout relativo). Risolto il problema.
1911z

@ 1911z stai dicendo che avevi il flag sul contenitore principale e rimuovendolo da lì ha risolto il problema?
RamPrasadBismil

14

Tra i motivi per cui chiunque può affrontare questo problema, controlla se hai impostato l'attributo android:animateLayoutChanges="true"su RecyclerView. Ciò impedirà il riciclaggio e il ricollegamento degli elementi di RecyclerView. Rimuovilo e assegna l'attributo al contenitore padre di RecyclerView, come LinearLayout / RelativeLayout e dovresti vedere il problema scomparire.


Ho visto questo arresto anomalo anche quando ho impostato l'attributo sul contenitore principale del camper.
RamPrasadBismil

@RamPrasadBismil Per favore inserisci il tuo codice e forse possiamo guardarlo?
Ram Iyer,

12

Mi ci sono voluti due giorni ma non sono riuscito a aggirare il problema, alla fine ho dovuto disabilitare il prefetch degli elementi.

Quando imposti il ​​layout manager puoi semplicemente chiamare

mGridLayoutManager.setItemPrefetchEnabled(false);

Mi ha fatto scomparire l'errore. Spero che sia utile per qualcuno.


Ha funzionato per me. Grazie.
Vicky

1
Sono davvero preoccupato per le persone che adottano questa soluzione. Perdi molto disabilitando questa bandiera e il bug è ancora in un altro posto -
Felipe Castilhos

8

Durante l'utilizzo di intestazioni adesive slimfit ho riscontrato questo errore. È stato causato dall'impostazione della prima posizione sbagliata. Ho la risposta qui

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

}

assicurati di passare il valore corretto per mSectionFirstPosition


Benvenuto in StackOverflow. Potresti fornire una risposta completa invece di un semplice link?
slfan

cosa c'è itemqui?
bug si verificano il

È l'elemento dell'elenco che deve essere mostrato nella vista riciclatore. Quindi fondamentalmente sto salvando la prima posizione della sezione rispetto a ciascun elemento dell'elenco.
Jaspinder Kaur

8

Ho riscontrato questo problema questa mattina, ma non sto affrontando lo stesso motivo di cui sopra.

Tramite il debug ho scoperto che la visualizzazione dell'elemento nel mio ViewHolder ha mParent e non è nulla, che normalmente non dovrebbe essere nessuno (questo è ciò che dice il registro, "la vista allegata potrebbe non essere riciclata", penso che significhi che se la vista figlio è già collegato a un genitore, potrebbe in qualche modo causare un fallimento durante il riciclaggio.)

Ma non ho collegato manualmente la visualizzazione figlio ogni volta. E ho scoperto che è fatto quando provo a gonfiare la visualizzazione figlio nel mio ViewHolder, qualcosa del tipo:

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

E l'ultimo parametro attachToRootdovrebbe essere falso.

Dopo averlo cambiato in false , ho risolto il problema.

A proposito, vedo questo arresto anomalo solo quando aggiorno la mia libreria di supporto all'ultima versione 25.0.0. Prima usavo la versione 23.4.0 e non vedo che questo problema si verifichi. Immagino che dovrebbe essere cambiato qualcosa nell'ultima libreria di supporto.

Spero che questo aiuto.


8

Ho anche riscontrato lo stesso errore durante lo scorrimento su RecyclerView: quindi l'ho rimosso animateLayoutChanges="true"nel file di layout per RecyclerViewpoi tutto ha funzionato.


6

Nel mio caso è successo perché avevo una Transitioncorsa durante il tentativo di ridimensionare RecyclerView perché la tastiera del software stava per mostrare.

L'ho risolto escludendo RecyclerView Transitionda utilizzando Transition.excludeTarget(R.id.recyclerview, true);


6

Esistono diversi motivi per cui viene chiamata questa eccezione. Nel mio caso era dovuto alle animazioni in esecuzione, ecco perché le visualizzazioni sono ancora allegate e non possono essere rimosse dalla visualizzazione. Solo quando l'animazione è terminata, la vista può essere rimossa e riciclata.

Esistono due tipi di animazione che potrebbero influire sul riciclaggio di Recyclerview.

1) È il file RecyclerView.ItemAnimator - questo non dovrebbe essere il problema. Questo dovrebbe essere abbastanza sicuro da usare in quanto controlla le viste attaccate e scartate e gestisce correttamente il riciclaggio.

2) android:animateLayoutChanges="true"o TransitionManager.beginDelayedTransition()o TransitionManager.go (), ecc. - Queste animazioni vengono eseguite da sole e acquisiscono gli elementi da animare. Questo risultato costringe le viste ad essere attaccate fino al termine dell'animazione. Il recyclerview non ha alcuna conoscenza di queste animazioni poiché è al di fuori del suo ambito. Pertanto si recyclerviewpotrebbe provare a riciclare un elemento pensando che potrebbe essere riciclato correttamente, ma il problema è che queste API continuano a mantenere le visualizzazioni fino al termine dell'animazione.

Se stai usando android:animateLayoutChanges="true"o TransitionManager.beginDelayedTransition()o TransitionManager.go (), ecc. Rimuovi semplicemente il fileRecyclerView e i suoi figli dall'animazione.

Puoi farlo semplicemente afferrando Transitione chiamando

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

Nota:

Tieni presente che è importante utilizzare Transition.excludeChildren()per escludere tutti i Recyclerviewbambini dall'animazione e non solo la Recyclerviewstessa.


Grazie! TransitionManager.beginDelayedTransition () è stata la causa del problema nel mio caso. Potresti aggiornare il tuo esempio di codice con maggiori dettagli su come usare Transition.excludeChildren. Istanziate un oggetto di transizione come:, val transition = AutoTransition()chiamate excludeChildren(recyclerView, true)questo oggetto e lo passate a beginDelayedTransaction() as the second parameter.
Danilo Prado

5

Anch'io ricevevo questo errore ogni volta che avevo animateLayoutChanges = "true" nel file di layout per RecyclerView. Elimina questo attributo e l'errore scomparirà!


Si prega di notare che questa domanda è del 2014 e le proprietà potrebbero essere cambiate a questo punto.
Korashen

2
No, non è ancora cambiato
Sanjay Kushwah

4

Mentre nel mio caso stava rimuovendo animateOnLayoutChangedal recyclerView che ha risolto il crash, avevo ancora bisogno della possibilità di animare le modifiche al layout all'interno del viewHolder. Per fare in modo che funzioni, LinearLayout' in the view holder needs theanimateOnLayoutChange 'su true, ma avevo bisogno notifyItemChangeddell'adattatore. Ciò ha quindi consentito l'avvio di entrambe le animazioni layoutTransition (per espandere e comprimere viewHolder) ed evita anche l'eccezione scartata. Quindi sì, evita di mettere animateOnLayoutChange su recylcerView e usa i vari metodi di notifica per abilitare le animazioni predefinite sulle modifiche alle dimensioni della vista.


Sto provando a fare la stessa cosa per animare l'espansione dell'elemento, ma la chiamata notifyItemChanged nell'adattatore fa lampeggiare l'elemento dopo la modifica (l'animazione funziona)! Come lo eviti?
Flyview

Per il nostro caso d'uso, abbiamo finito per sostituire il nostro codice personalizzato utilizzando animateOnLayoutChange, per utilizzare un layout espandibile. Realizza la stessa cosa che stavamo cercando di realizzare ma molto più flessibile. github.com/chuross/expandable-layout
kingargyle

3

Risolvo questo problema rimuovendo parent.addView() inonCreateViewHolder

Questo è il mio codice

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

Funzione per android.support.v7.widget.RecyclerViewRecycler.recyclerViewHolderinternal()verificare se il mio pulsante ha già un genitore o meno. Che se aggiungiamo il pulsante al genitore, verrà assegnato anche RecyclerViewalla sua mParentvariabile.


Penso che sia un nuovo requisito, ho fatto allegare al genitore nella versione 24 del supporto della libreria, una volta aggiornato alla 25 ho avuto il crash.
Kirill Kulakov

1

Ho visto questo accadere per me quando ho usato un oggetto personalizzato in ViewHolderper ilRecyclerView adattatore.

Per risolvere il problema, ho cancellato l'oggetto personalizzato che nel mio caso era un timer onViewRecycled(ViewHolder holder)per l'adattatore come di seguito:

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

Questo ha risolto il bug.


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: rimuove i dati dall'elenco.

2 、notifyDataSetChanged: notifyDataSetChanged ();

3 、notifyItemRemoved: mostra l'animazione.

4 、notifyItemRangeChanged: dimensione della visualizzazione della gamma e ridisegnare il fileviewHolders(onBindViewHolder methods)


Ho fatto notifyItemRemoveddurante la rimozione footere l'app si blocca, cambiarlo in notifyDataSetChangede ora funziona bene. grazie
Siarhei

1

Nel mio caso, ho usato TransitionManager.beginDelayedTransition()prima di aggiungere una vista in cima a recyclerView. Ho rimosso TransitionManager.beginDelayedTransition()e nessun arresto anomalo.


1

Ho risolto questo problema chiamando

setHasStableIds(true);

nel costruttore dell'adattatore e sovrascrivendo getItemIdnell'adattatore:

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

1

Rimuovi android:animateLayoutChanges="true"da Recycleview o impostaandroid:animateLayoutChanges="false"


0

Io uso com.squareup.picasso.RequestCreator

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

per ridimensionare dinamicamente le dimensioni di ImageView dopo aver scaricato l'immagine da Internet e salva la larghezza e l'altezza ridimensionate per preservare le dimensioni della vista. Ho ricevuto questa eccezione perché l'ho salvata LayoutParamsin a Map, e nel mio onBindViewHolder, l'ho recuperata e impostata direttamente sul mio ImageView. Lo risolvo utilizzando ImmutablePair<Integer, Integer>per memorizzare solo le dimensioni di ImageView piuttosto che molti altri stati e utilizzare il seguente codice per ripristinarlo.

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

0

Per me lo stesso bug è stato causato da un LayoutTransition su un ViewGroup di livello superiore.


0

Vorrei aggiungere un'altra possibile soluzione per questo tipo di problema, per favore. Ho avuto lo stesso problema con la libreria superSlim per intestazioni appiccicose in formato RecyclerView. Ho usato MatrixCursorper impostare i dati su RecyclerViewCursorAdapter. Il motivo di questo problema era che le colonne ID uguale a 0per tutte le intestazioni. Spero che questo possa aiutare qualcuno a risparmiare un paio di giorni di debug.


0

Nel mio caso il problema era dovuto all'implementazione errata di questo metodo public long getItemId(int position)(sovrascritto dal RecyclerView.Adaptermetodo).

Il vecchio codice otterrà due ID diversi per lo stesso elemento (nel mio caso è l'elemento del piè di pagina), dopo aver corretto l'implementazione il problema è scomparso.


0

Soluzione alternativa se il motivo dell'eccezione è ciò che itemView ha padre. Nel codice, dove si dispone di notifyItemRemoved (posizione), rimuovere itemView da RecyclerView:

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

0

Un caso particolare che si è verificato per me è stato che avevo un membro della vista nell'adattatore ed ero pigro nell'istanziare una vista che non è necessario fare con la vista del riciclo.

Va anche contro i principi delle viste di riciclo che come la memorizzazione di un riferimento alla vista in questo caso. Faccio un rapido esempio di seguito:

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

questa eccezione non è causa di

Android: animateLayoutChanges

o

Android: focusableInTouchMode

questa risposta finale corretta è solo perché hai impostato un LayoutParams SBAGLIATO .

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

il nameLP è OK. il nameLP2 si verifica il crash .bug è qui.

Provo tutte le risposte di questa pagina. fidati di me.


0

Ho avuto questo problema perché ho sovrascrivere equals()e hashcode()metodo ViewHolderdi RecyclerView.ViewHolder calcolando l'uguaglianza di dati e codice hash, quindi la logica di riciclo non ha funzionato e sono caduto, mi è sufficiente rimuovere la sovrascrittura e fissa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.