Comprender RecyclerView setHasFixedSize

136

Estoy teniendo problemas para entender setHasFixedSize(). Sé que se usa para la optimización cuando el tamaño de RecyclerViewno cambia, desde los documentos.

Pero, ¿qué significa eso? En los casos más comunes, ListViewcasi siempre tiene un tamaño fijo. ¿En qué casos no sería un tamaño fijo? ¿Significa que el espacio real que ocupa en la pantalla crece con el contenido?

SIR Codealot
fuente
Encontré que esta respuesta es útil y muy fácil de entender [StackOverflow - rv.setHasFixedSize (true); ] ( stackoverflow.com/questions/28827597/… )
Mustafa Hasan

Respuestas:

115

Una versión muy simplificada de RecyclerView tiene:

void onItemsInsertedOrRemoved() {
   if (hasFixedSize) layoutChildren();
   else requestLayout();
}

Este enlace describe por qué las llamadas requestLayoutpueden ser costosas. Básicamente, cada vez que se insertan, mueven o eliminan elementos, el tamaño (ancho y alto) de RecyclerView puede cambiar y, a su vez, puede cambiar el tamaño de cualquier otra vista en la jerarquía de vistas. Esto es particularmente problemático si los elementos se agregan o eliminan con frecuencia.

Evite pases de diseño innecesarios estableciéndolo setHasFixedSizeen verdadero cuando cambiar el contenido del adaptador no cambia su altura o el ancho.


Actualización: JavaDoc se ha actualizado para describir mejor lo que realmente hace el método.

RecyclerView puede realizar varias optimizaciones si puede saber de antemano que el tamaño del RecyclerView no se ve afectado por el contenido del adaptador. RecyclerView aún puede cambiar su tamaño en función de otros factores (por ejemplo, el tamaño de su padre), pero este cálculo de tamaño no puede depender del tamaño de sus elementos secundarios o del contenido de su adaptador (excepto el número de elementos en el adaptador).

Si su uso de RecyclerView cae en esta categoría, configúrelo en {@code true}. Permitirá que RecyclerView evite invalidar todo el diseño cuando cambie el contenido de su adaptador.

@param hasFixedSize true si los cambios del adaptador no pueden afectar el tamaño de RecyclerView.

LukaCiko
fuente
162
El tamaño de RecyclerView cambia cada vez que agrega algo sin importar qué. Lo que hace setHasFixedSize es asegurarse (por entrada del usuario) de que este cambio de tamaño de RecyclerView es constante. La altura (o ancho) del artículo no cambiará. Cada elemento agregado o eliminado será el mismo. Si no configura esto, verificará si el tamaño del artículo ha cambiado y eso es costoso. Solo aclarando porque esta respuesta es confusa.
Arnold Balliu
9
@ArnoldB excelente aclaración. Incluso lo discutiría como una respuesta independiente.
young_souvlaki
44
@ArnoldB: sigo confundido. ¿Está sugiriendo que deberíamos establecer hasFixedSize en true si los anchos / alturas de todos los niños son constantes? En caso afirmativo, ¿qué pasa si existe la posibilidad de que algunos niños puedan ser eliminados en tiempo de ejecución (tengo que deslizar para descartar la función)? ¿Está bien establecerlo como verdadero?
Jaguar
1
Si. Porque el ancho y la altura del artículo no cambian. Solo se está agregando o eliminando. Agregar o quitar elementos no cambia su tamaño.
Arnold Balliu
3
@ArnoldB No creo que el tamaño (ancho / alto) del artículo sea un problema aquí. Tampoco verificará el tamaño del artículo. Simplemente le dice a RecyclerView que llame requestLayouto no después de que el conjunto de datos se haya actualizado.
Kimi Chiu el
22

Puede confirmar se setHasFixedSizerelaciona con el RecyclerView en sí, y no con el tamaño de cada elemento adaptado a él.

Ahora puede usarlo android:layout_height="wrap_content"en un RecyclerView, que, entre otras cosas, permite que CollapsingToolbarLayout sepa que no debe colapsar cuando RecyclerView está vacío. Esto solo funciona cuando se usa setHasFixedSize(false)en RecylcerView.

Si lo usa setHasFixedSize(true)en RecyclerView, este comportamiento para evitar el colapso de CollapsingToolbarLayout no funciona, aunque RecyclerView esté vacío.

Si setHasFixedSizeestaba relacionado con el tamaño de los elementos, no debería tener ningún efecto cuando RecyclerView no tiene elementos.

Kevin
fuente
44
Acabo de tener una experiencia que apunta a la misma dirección. Usando un RecyclerView con un GridLayoutManager (3 elementos por fila) y layout_height = wrap_content. Cuando hago clic en un botón que agrega 3 elementos nuevos a la lista, la vista del reciclador no se expande para ajustarse a los elementos nuevos. Más bien, mantiene el mismo tamaño, y la única forma de ver los nuevos elementos es desplazándolo. Aunque los elementos tienen el mismo tamaño, tuve que eliminarlos setHasFixedSize(true)para expandirlos cuando se agregan nuevos elementos.
Mateus Gondim
Creo que tienes razón. Desde el documento, por hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView.lo que incluso si el tamaño del elemento cambiará, aún puede establecer esto en verdadero.
Kimi Chiu
12

ListView tenía una función con nombre similar que creo que reflejaba información sobre el tamaño de las alturas de los elementos de la lista individual. La documentación de RecyclerView indica claramente que se refiere al tamaño de RecyclerView en sí, no al tamaño de sus elementos.

Del comentario de la fuente RecyclerView sobre el método setHasFixedSize ():

 * RecyclerView can perform several optimizations if it can know in advance that changes in
 * adapter content cannot change the size of the RecyclerView itself.
 * If your use of RecyclerView falls into this category, set this to true.
dangVarmit
fuente
16
Pero, ¿cómo se define el "tamaño" de RecyclerView? ¿Es el tamaño visible solo en la pantalla, o el tamaño completo de RecyclerView, que es igual (suma de alturas de elementos + relleno + espaciado)?
Vicky Chijwani
2
De hecho, esto necesita más información. Si elimina elementos y la vista de reciclado se reduce, ¿se considera que ha cambiado de tamaño?
Henrique de Sousa
44
Pensaría en ello como un TextView puede presentarse. Si especifica wrap_content, cuando establece el texto, TextView puede solicitar un pase de diseño y cambiar la cantidad de espacio que ocupa en la pantalla. Si especifica match_parent o una dimensión fija, TextView no solicitará un pase de diseño porque el tamaño es fijo y la cantidad de texto insde nunca cambiará la cantidad de espacio ocupado. RecyclerView es lo mismo. setHasFixedSize () sugiere a RV que nunca debería necesitar solicitar pases de diseño basados ​​en cambios en los elementos del adaptador.
dangVarmit
1
@dangVarmit buena explicación!
howerknea
6

Wen fijamos setHasFixedSize(true)en RecyclerViewque los medios de tamaño reciclador es fijo y no se ve afectada por el contenido de adaptador. Y en este caso onLayoutno se llama al reciclador cuando actualizamos los datos del adaptador (pero hay una excepción).

Vayamos al ejemplo:

RecyclerViewtiene un RecyclerViewDataObserver( encontrar implementación predeterminada en este archivo ) con varios métodos, el principal importante es:

void triggerUpdateProcessor() {
    if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
        ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
    } else {
        mAdapterUpdateDuringMeasure = true;
        requestLayout();
    }
}

Este método se llama si establecemos setHasFixedSize(true)y actualizar los datos de un adaptador a través de: notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. En este caso no hay llamadas al reciclador onLayout, pero hay llamadas a requestLayoutactualizar niños.

Pero si configuramos setHasFixedSize(true)y actualizamos los datos de un adaptador a través de, notifyItemChangedentonces hay una llamada al onChangevalor predeterminado del reciclador RecyclerViewDataObservery no hay llamadas a triggerUpdateProcessor. En este caso, onLayoutse llama al reciclador cada vez que configuramos setHasFixedSize trueo false.

// no calls to triggerUpdateProcessor
@Override
public void onChanged() {
    assertNotInLayoutOrScroll(null);
     mState.mStructureChanged = true;

     processDataSetCompletelyChanged(true);
     if (!mAdapterHelper.hasPendingUpdates()) {
         requestLayout();
     }
}

// calls to triggerUpdateProcessor
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    assertNotInLayoutOrScroll(null);
    if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
        triggerUpdateProcessor();
    }
}

Cómo verificar por ti mismo:

Crear personalizado RecyclerViewy anular:

override fun requestLayout() {
    Log.d("CustomRecycler", "requestLayout is called")
    super.requestLayout()
}

override fun invalidate() {
    Log.d("CustomRecycler", "invalidate is called")
    super.invalidate()
}

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    Log.d("CustomRecycler", "onLayout is called")
    super.onLayout(changed, l, t, r, b)
}

Establezca el tamaño del reciclador en match_parent(en xml). Intente actualizar los datos del adaptador usando replaceDatay replaceOne con seting setHasFixedSize(true)y luego false.

// onLayout is called every time
fun replaceAll(data: List<String>) {
    dataSet.clear()
    dataSet.addAll(data)
    this.notifyDataSetChanged()
}

// onLayout is called only for setHasFixedSize(false)
fun replaceOne(data: List<String>) {
    dataSet.removeAt(0)
    dataSet.addAll(0, data[0])
    this.notifyItemChanged(0)
}

Y revisa tu registro.

Mi registro:

// for replaceAll
D/CustomRecycler: requestLayout is called
D/CustomRecycler: onMeasure is called
D/CustomRecycler: onMeasure is called
D/CustomRecycler: onLayout
D/CustomRecycler: requestLayout is called
D/CustomRecycler: requestLayout is called
D/CustomRecycler: onDraw is called

// for replaceOne
D/CustomRecycler: requestLayout is called
D/CustomRecycler: onDraw is called
D/CustomRecycler: requestLayout is called
D/CustomRecycler: onDraw is called

Resumir:

Si configuramos setHasFixedSize(true)y actualizamos los datos del adaptador notificando a un observador de otra manera que no sea la llamada notifyDataSetChanged, entonces tiene un poco de rendimiento, porque no hay llamadas al onLayoutmétodo de reciclador .

bitvale
fuente
¿Has probado con RecyclerView de altura usando wrap_content o match_parent?
Lubos Mudrak
6

Si tenemos una RecyclerViewcon match_parenttan alto / ancho , hay que añadir setHasFixedSize(true)ya que el tamaño de la RecyclerViewmisma no cambia de insertar o eliminar elementos en ella.

setHasFixedSize debe ser falsa si tenemos una RecyclerView con wrap_contenttan alto / ancho ya que cada elemento insertado por el adaptador podría cambiar el tamaño de la Recyclerfunción de los elementos insertados / borrado, por lo que, el tamaño de la Recyclerque será diferente cada vez que añadir / eliminar artículos.

Para ser más claro, si usamos

<android.support.v7.widget.RecyclerView
    android:id="@+id/my_recycler_view"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

Nosotros podemos usar my_recycler_view.setHasFixedSize(true)

<android.support.v7.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

Deberíamos usar my_recycler_view.setHasFixedSize(false), esto aplica si también usamos wrap_contentcomo ancho

Gastón Saillén
fuente
3

setHasFixedSize (true) significa que RecyclerView tiene elementos secundarios (elementos) que tienen ancho y alto fijos. Esto permite que RecyclerView se optimice mejor al calcular la altura y el ancho exactos de toda la lista en función de su adaptador.

Parque Calvin
fuente
55
Eso no es lo que ha sugerido @dangVarmit.
extraño veces
55
Engañoso, en realidad es el tamaño de la vista del Reciclador, no el tamaño del contenido
Benoit
0

Afecta las animaciones de la vista del reciclador, si es false... las animaciones de inserción y eliminación no se mostrarán. así que asegúrese de que sea trueen caso de que haya agregado animación para la vista del reciclador.

Alaa AbuZarifa
fuente
0

Si el tamaño de RecyclerView (el RecyclerView en sí)

... no depende del contenido del adaptador:

mRecyclerView.setHasFixedSize(true);

... depende del contenido del adaptador:

mRecyclerView.setHasFixedSize(false);
Alok Singh
fuente