RecyclerView se bloquea cuando "es posible que las vistas eliminadas o adjuntas no se puedan reciclar"

115

Estoy usando una implementación simple de RecyclerViewtomado del sitio web de Android usando a StaggeredGridLayoutManagery sigo recibiendo este error que bloquea mi aplicación:

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 simple, literalmente quiero decir que es la misma implementación tomada de esta página en su sitio web , la única diferencia es que el diseño de mi elemento de cuadrícula es un ImageViewpar de TextViews, por lo que no me molestaré en volver a publicar mi código.

¿Alguien más recibe este error y sabe cómo solucionarlo?

StackOverflowMaster
fuente
¿Tienes alguna solución?
Pratik Butani

Respuestas:

191

Este error se produce si en su XML ha android:animateLayoutChangesestablecido en verdadero y llama notifyDataSetChanged()al adaptador de RecyclerView en el código Java.

Por lo tanto, evite usar android:animateLayoutChangescon RecyclerViews.

StackOverflowMaster
fuente
22
entonces, ¿cómo se puede utilizar la función animateLayoutChanges en la vista de reciclaje?
dhuma1981
¿Qué estás intentando lograr? Animación del artículo Si es así, la API RecyclerView admite esto; eche un vistazo a la documentación: developer.android.com/reference/android/support/v7/widget/…
Kenneth
4
@ dhuma1981 si el animador de elementos se configura a través de mRecyclerView.setItemAnimator (new DefaultItemAnimator ()); entonces, animateLayoutChanges no necesita ser cierto
Rich Ehmer
RecyclerViewutiliza DefaultItemAnimatorpor defecto.
Benjamin
-, - tengo este problema exactamente como lo describiste
Ninja Coding
52

También tuve que lidiar con este accidente y, en mi caso, no tuvo nada que ver android:animateLayoutChanges.

El RecyclerViewedificio que estábamos construyendo tenía más de un tipo de vistas y algunas tenían EditTexts en ellas. Después de un tiempo, identificamos el problema como relacionado con el enfoque. Este error ocurre mientras se reciclan correos EditTextelectrónicos y uno de ellos está enfocado.

Naturalmente, intentamos borrar el enfoque cuando se vinculan nuevos datos a una vista reciclada, pero eso no funcionó hasta que android:focusableInTouchMode="true"se configuró RecycleView. En realidad, ese es el único cambio que se necesitó al final para que este problema desapareciera.

Nemanja Kovacevic
fuente
2
Fantásticos problemas relacionados con múltiples enfoques resueltos que tuve al usar EditTexts en un RecyclerView. ¡Gracias!
Rabie Jradi
1
Tenía ACET en Recyclerview, y aplasta. Esta publicación me salvó.
Kai Wang
Y no tengo textos de edición en los elementos, aunque tengo casillas de verificación. Debo intentarlo android:focusableInTouchMode="true"porque sucede solo a veces en algunos dispositivos (rara vez) y supongo que no se relaciona con mi problema, pero el seguimiento de la pila para el bloqueo es casi el mismo.
Shivansh
Este era mi caso, pero el entorno no android:focusableInTouchMode="true"me ayudó en absoluto. Así que limpié el enfoque en la onViewDetachedFromWindowdevolución de llamada. public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { if (holder instanceof ItemViewHolder) { ItemViewHolder item = (ItemViewHolder) holder; if (item.editText.isFocused()) item.editText.clearFocus(); } }
artman
24

Eliminé la android:animateLayoutChangespropiedad de diseño y el problema se resolvió.

Özer Özcan
fuente
También comencé a tener este choque cuando coloqué el android:animateLayoutChangesen mi RV.
Mauker
2
Tenía esta bandera en el contenedor principal (diseño relativo). Se solucionó el problema.
1911z
@ 1911z, ¿estás diciendo que tenías la bandera en el contenedor principal y que al quitarla de allí se solucionó el problema?
RamPrasadBismil
14

Entre las razones por las que cualquiera puede enfrentar este problema, verifique si ha establecido el atributo android:animateLayoutChanges="true"en RecyclerView. Esto hará que el reciclaje y la reinstalación de los elementos de RecyclerView fallen. Elimínelo y asigne el atributo al contenedor principal de RecyclerView, como LinearLayout / RelativeLayout y debería ver que el problema desaparece.

Ram Iyer
fuente
Vi este bloqueo incluso cuando configuré el atributo en el contenedor principal del RV.
RamPrasadBismil
@RamPrasadBismil Por favor, publique su código y tal vez podamos verlo.
Ram Iyer
12

Me tomó dos días pero no pude evitar esto, al final, tuve que deshabilitar la captación previa de elementos.

Al configurar el administrador de diseño, simplemente puede llamar

mGridLayoutManager.setItemPrefetchEnabled(false);

Hizo que el error desapareciera para mí. Espero que sea útil para alguien.

Max
fuente
Trabajó para mi. Gracias.
Vicky
1
Me preocupa mucho que la gente adopte esta solución. Pierdes mucho deshabilitando esta bandera y el error sigue en otro lugar -
Felipe Castilhos
8

Mientras usaba encabezados adhesivos slimfit, encontré este error. Fue causado por establecer una primera posición incorrecta. Tengo la respuesta aqui

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

}

solo asegúrese de pasar el valor correcto para mSectionFirstPosition

Jaspinder Kaur
fuente
Bienvenido a StackOverflow. ¿Podría proporcionar una respuesta completa en lugar de solo un enlace?
slfan
que hay itemaqui
Suceden errores
Es el elemento de la lista que se mostrará en la vista del reciclador. Básicamente, estoy guardando la primera posición de la sección en cada elemento de la lista.
Jaspinder Kaur
8

Me encontré con este problema esta mañana, pero no estoy enfrentando la misma razón que se mencionó anteriormente.

A través de la depuración, encontré que la vista del elemento en mi ViewHolder tiene mParenty no es nula, que en caso normal no debería ser ninguna (eso es lo que dice el registro, "la vista adjunta no se puede reciclar", creo que significa que si la vista secundaria ya está adjunto a un padre, de alguna manera causaría fallas al reciclar).

Pero no adjunté la vista infantil cada vez de forma manual. Y descubrí que está hecho cuando trato de inflar la vista infantil en mi ViewHolder, algo como:

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

Y el último parámetro attachToRootdebería ser falso.

Después de cambiarlo a false, solucioné mi problema.

Por cierto, solo veo que este bloqueo ocurrió cuando actualizo mi biblioteca de soporte a la última versión 25.0.0. Antes usaba la versión 23.4.0 y no veo que ocurra este problema. Supongo que debería haber algo cambiado en la última biblioteca de soporte.

Espero que esto ayude.

Anthonyeef
fuente
8

También encontré el mismo error al desplazarme por RecyclerView: luego eliminé el animateLayoutChanges="true"archivo de diseño para que RecyclerViewtodo funcionara.

usuario8796389
fuente
6

En mi caso, sucedió porque tuve una Transitionejecución al intentar cambiar el tamaño de RecyclerView porque el teclado del software estaba a punto de mostrarse.

Lo arreglé excluyendo el RecyclerView del Transitionusando Transition.excludeTarget(R.id.recyclerview, true);

Tunji_D
fuente
6

Hay varias razones por las que se llama a esta excepción. En mi caso, se debió a la ejecución de animaciones, por eso las vistas aún están adjuntas y no se pudieron eliminar a la vista. Solo cuando la animación haya terminado, la vista podría eliminarse y reciclarse.

Hay dos tipos de animación que podrían afectar el reciclaje de la vista del reciclador.

1) es el RecyclerView.ItemAnimator - este no debería ser el problema. Esto debería ser bastante seguro de usar, ya que verifica las vistas adjuntas y desechadas y maneja el reciclaje correctamente.

2) android:animateLayoutChanges="true"o TransitionManager.beginDelayedTransition()TransitionManager.go (), etc. - Estas animaciones se ejecutan por sí solas y se apoderan de los elementos para animar. Esto da como resultado que las vistas se obliguen a adjuntar hasta que finalice la animación. La vista del reciclaje no tiene ningún conocimiento de estas animaciones ya que está fuera de su alcance. Por lo tanto, es recyclerviewposible que intenten reciclar un elemento pensando que podría reciclarse correctamente, pero el problema es que estas API todavía se aferran a las vistas hasta que finaliza la animación.

Si está utilizando android:animateLayoutChanges="true"o TransitionManager.beginDelayedTransition()o TransitionManager.go (), etc., simplemente elimine elRecyclerView y sus hijos de la animación.

Simplemente puede hacer esto tomando el Transitiony llamando

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

Nota:

Tenga en cuenta que es importante utilizar Transition.excludeChildren()para excluir a todos los Recyclerviewniños de la animación y no solo a ellos Recyclerviewmismos.

Archie G. Quiñones
fuente
¡Gracias! TransitionManager.beginDelayedTransition () fue la causa del problema en mi caso. Puede actualizar su ejemplo de código con más detalles sobre cómo usarlo Transition.excludeChildren. Crea una instancia de un objeto de transición como:, val transition = AutoTransition()invoca excludeChildren(recyclerView, true)este objeto y se lo pasa a beginDelayedTransaction() as the second parameter.
Danilo Prado
5

Yo también recibía este error cada vez que tenía animateLayoutChanges = "true" en el archivo de diseño para RecyclerView. Elimina este atributo y el error desaparecerá.

rvd
fuente
Tenga en cuenta que esta pregunta es de 2014 y es posible que las propiedades ya hayan cambiado.
Korashen
2
No, aún no ha cambiado
Sanjay Kushwah
4

Si bien en mi caso fue la eliminación animateOnLayoutChangede RecyclerView lo que solucionó el bloqueo, todavía necesitaba la capacidad de animar los cambios de diseño dentro de viewHolder. Para que esto funcione, LinearLayout' in the view holder needs theanimateOnLayoutChange 'a verdadero, pero necesitaba hacerlo notifyItemChangedcon el adaptador. Esto permitió que se iniciaran ambas animaciones de layoutTransition (para expandir y contraer viewHolder) y también evita la excepción desechada. Entonces, sí, evite poner animateOnLayoutChange en recylcerView y use los diversos métodos de notificación para habilitar las animaciones predeterminadas en los cambios de tamaño de la vista.

kingargyle
fuente
Estoy tratando de hacer lo mismo para animar la expansión del elemento, pero llamar a notifyItemChanged en el adaptador hace que el elemento parpadee después del cambio (la animación funciona). ¿Cómo evitas eso?
Flyview
Para nuestro caso de uso, terminamos cambiando nuestro código personalizado usando animateOnLayoutChange, para usar un diseño expandible. Logra lo mismo que estábamos tratando de lograr, pero es mucho más flexible. github.com/chuross/expandable-layout
kingargyle
3

Soluciono este problema quitando parent.addView()enonCreateViewHolder

Este es mi codigo

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

Función en android.support.v7.widget.RecyclerViewRecycler.recyclerViewHolderinternal()comprobar si mi botón ya tiene un padre o no. Que si agregamos el botón al padre, también se asignará RecyclerViewa su mParentvariable.

egon12
fuente
Creo que es un nuevo requisito, lo adjunté a los padres en la versión 24 del soporte de la biblioteca, una vez actualizado a 25 obtuve el bloqueo.
Kirill Kulakov
1

Vi que esto me sucedió cuando usé un objeto personalizado en ViewHolderel RecyclerViewadaptador.

Para solucionar el problema, borré el objeto personalizado que en mi caso era un temporizador en onViewRecycled(ViewHolder holder)el adaptador de la siguiente manera:

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

Esto solucionó el error.

Androidrp
fuente
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);
        }
    }
ZhangTengyuan
fuente
1

1 、remove: eliminar datos de la lista.

2 、notifyDataSetChanged: notificarDataSetChanged ();

3 、notifyItemRemoved: muestra la animación.

4 、notifyItemRangeChanged: rango de tamaño de vista y vuelva a dibujar elviewHolders(onBindViewHolder methods)

ZhangTengyuan
fuente
Lo hice notifyItemRemovedal eliminar footery la aplicación se bloquea, cámbielo a notifyDataSetChangedy ahora funciona bien. gracias
Siarhei
1

En mi caso, usé el TransitionManager.beginDelayedTransition()antes de agregar una vista en la parte superior de la vista de reciclaje. Quité el TransitionManager.beginDelayedTransition()y no choqué.

Hai nguyen thanh
fuente
1

Resolví este problema llamando

setHasStableIds(true);

en el constructor del adaptador y anulando getItemIden el adaptador:

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

Eliminar android:animateLayoutChanges="true"de la vista de reciclaje o configurarandroid:animateLayoutChanges="false"

Rajesh Nasit
fuente
0

yo suelo com.squareup.picasso.RequestCreator

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

para redimensionar dinámicamente el tamaño de ImageView después de descargar la imagen de Internet, y guarda el ancho y alto redimensionados para preservar el tamaño de la vista. Obtuve esta excepción porque guardé LayoutParamsen a Map, y en mi onBindViewHolder, la recuperé y la configuré directamente en mi ImageView. Soluciono esto mediante el uso ImmutablePair<Integer, Integer>para almacenar solo el tamaño de ImageView en lugar de muchos otros estados, y uso el siguiente código para restaurarlo.

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

Para mí, el mismo error fue causado por LayoutTransition en un ViewGroup de nivel superior.

Mattlaabs
fuente
0

Permítanme agregar otra posible solución para este tipo de problema, por favor. Tuve el mismo problema con la biblioteca superSlim para encabezados pegajosos en RecyclerView. Solía MatrixCursorconfigurar los datos en RecyclerViewCursorAdapter. El motivo de este problema es que las columnas de ID son iguales a 0para todos los encabezados. Espero que eso ayude a alguien a ahorrar un par de días de depuración.

MistaGreen
fuente
0

En mi caso, el problema se debió a la implementación incorrecta de este método public long getItemId(int position)(anulado del RecyclerView.Adaptermétodo).

El código anterior obtendrá dos ID diferentes para el mismo elemento (en mi caso, es el elemento del pie de página), después de corregir la implementación, el problema desapareció.

Mu Sa
fuente
0

Solución alternativa si el motivo de la excepción es lo que itemView tiene como padre. En el código, donde ha notificadoItemRemoved (posición), elimine itemView de RecyclerView:

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

Un caso peculiar que me sucedió fue que tenía un miembro de vista en el adaptador y era perezoso instanciar una vista que no es necesario hacer con la vista de reciclaje.

También va en contra de los principios de reciclado de vistas que, en este caso, almacenan una referencia a la vista. Doy un ejemplo rápido a continuación:

// 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
Jonny2Plates
fuente
0

esta excepción no es causa de

android: animateLayoutChanges

o

android: focusableInTouchMode

esta última respuesta correcta es simplemente porque estableció un LayoutParams INCORRECTO .

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

el nombre LP está bien. el nameLP2 ocurre el crash .bug está aquí.

Intento todas las respuestas de esta página. Créeme.

evin
fuente
0

Tenía este problema porque sobreescribo equals()y hashcode()método ViewHolderde RecyclerView.ViewHolder mediante el cálculo de la igualdad de datos y código hash, entonces la lógica de reciclaje no funcionaba y choqué, me quito la sobrescritura y fija.

Irwin
fuente