Comprendre RecyclerView setHasFixedSize

136

J'ai du mal à comprendre setHasFixedSize(). Je sais qu'il est utilisé pour l'optimisation lorsque la taille de RecyclerViewne change pas, d'après les documents.

Mais qu'est-ce que cela signifie? Dans la plupart des cas, un a ListViewpresque toujours une taille fixe. Dans quels cas ne serait-ce pas une taille fixe? Cela signifie-t-il que l'immobilier réel qu'il occupe à l'écran grandit avec le contenu?

SIr Codealot
la source
J'ai trouvé cette réponse utile et très facile à comprendre [StackOverflow - rv.setHasFixedSize (true); ] ( stackoverflow.com/questions/28827597/… )
Mustafa Hasan

Réponses:

115

Une version très simplifiée de RecyclerView a:

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

Ce lien explique pourquoi les appels requestLayoutpeuvent être coûteux. Fondamentalement, chaque fois que des éléments sont insérés, déplacés ou supprimés, la taille (largeur et hauteur) de RecyclerView peut changer et, à son tour, la taille de toute autre vue dans la hiérarchie des vues peut changer. Ceci est particulièrement gênant si des éléments sont ajoutés ou supprimés fréquemment.

Évitez les passes de mise en page inutiles en définissant setHasFixedSizesur true lorsque la modification du contenu de l'adaptateur ne modifie ni sa hauteur ni sa largeur.


Mise à jour: Le JavaDoc a été mis à jour pour mieux décrire ce que fait réellement la méthode.

RecyclerView peut effectuer plusieurs optimisations s'il sait à l'avance que la taille de RecyclerView n'est pas affectée par le contenu de l'adaptateur. RecyclerView peut toujours modifier sa taille en fonction d'autres facteurs (par exemple, la taille de son parent) mais ce calcul de taille ne peut pas dépendre de la taille de ses enfants ou du contenu de son adaptateur (à l'exception du nombre d'éléments dans l'adaptateur).

Si votre utilisation de RecyclerView entre dans cette catégorie, définissez-la sur {@code true}. Cela permettra à RecyclerView d'éviter d'invalider la mise en page entière lorsque le contenu de son adaptateur change.

@param hasFixedSize true si les modifications de l'adaptateur ne peuvent pas affecter la taille de RecyclerView.

LukaCiko
la source
162
La taille de RecyclerView change chaque fois que vous ajoutez quelque chose, quoi qu'il arrive. Ce que fait setHasFixedSize, c'est qu'il s'assure (par entrée utilisateur) que ce changement de taille de RecyclerView est constant. La hauteur (ou largeur) de l'élément ne changera pas. Chaque élément ajouté ou supprimé sera le même. Si vous ne définissez pas cela, il vérifiera si la taille de l'article a changé et c'est cher. Juste clarifier parce que cette réponse est déroutante.
Arnold Balliu
9
@ArnoldB excellente clarification. Je dirais même que c'est une réponse autonome.
young_souvlaki
4
@ArnoldB - Je suis toujours confus. Suggérez-vous de définir hasFixedSize sur true si les largeurs / hauteurs de tous les enfants sont constantes? Si oui, que se passe-t-il s'il est possible que certains enfants puissent être supprimés au moment de l'exécution (j'ai un balayage pour ignorer la fonction) - est-il correct de définir true?
Jaguar
1
Oui. Parce que la largeur et la hauteur de l'élément ne changent pas. Il vient d'être ajouté ou supprimé. L'ajout ou la suppression d'éléments ne modifie pas leur taille.
Arnold Balliu
3
@ArnoldB Je ne pense pas que la taille (largeur / hauteur) de l'article soit un problème ici. Il ne vérifie pas non plus la taille de l'article. Il indique simplement au RecyclerView d'appeler requestLayoutou non une fois que le dataSet a été mis à jour.
Kimi Chiu
22

Peut confirmer setHasFixedSizeconcerne le RecyclerView lui-même, et non la taille de chaque élément qui lui est adapté.

Vous pouvez maintenant utiliser android:layout_height="wrap_content"sur un RecyclerView, qui, entre autres, permet à un CollapsingToolbarLayout de savoir qu'il ne doit pas se réduire lorsque le RecyclerView est vide. Cela ne fonctionne que lorsque vous utilisez setHasFixedSize(false)sur le RecylcerView.

Si vous utilisez setHasFixedSize(true)sur le RecyclerView, ce comportement pour empêcher le CollapsingToolbarLayout de se réduire ne fonctionne pas, même si le RecyclerView est effectivement vide.

Si setHasFixedSizeétait lié à la taille des éléments, cela ne devrait avoir aucun effet lorsque RecyclerView ne contient aucun élément.

Kevin
la source
4
Je viens de vivre une expérience qui va dans le même sens. Utilisation d'un RecyclerView avec un GridLayoutManager (3 éléments par ligne) et layout_height = wrap_content. Lorsque je clique sur un bouton qui ajoute 3 nouveaux éléments à la liste, la vue du recycleur ne se développe pas pour s'adapter aux nouveaux éléments. Au contraire, il conserve la même taille et la seule façon de voir les nouveaux éléments est de les faire défiler. Même si les éléments ont la même taille, j'ai dû les supprimer setHasFixedSize(true)pour les agrandir lorsque de nouveaux éléments sont ajoutés.
Mateus Gondim
Je pense que tu as raison. À partir du document, hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView.donc même si la taille de l'élément change, vous pouvez toujours définir cela sur true.
Kimi Chiu le
12

Le ListView avait une fonction nommée similaire qui, je pense, reflétait des informations sur la taille des hauteurs individuelles des éléments de liste. La documentation de RecyclerView indique clairement qu'il fait référence à la taille du RecyclerView lui-même, et non à la taille de ses éléments.

À partir du commentaire source de RecyclerView au-dessus de la méthode 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
la source
16
Mais comment la "taille" de RecyclerView est-elle définie? Est-ce la taille visible à l'écran uniquement ou la taille totale de RecyclerView, qui est égale à (somme des hauteurs des éléments + remplissage + espacement)?
Vicky Chijwani
2
En effet, cela nécessite plus d'informations. Si vous supprimez des articles et que la vue de recyclage diminue, est-il considéré comme ayant changé de taille?
Henrique de Sousa
4
Je penserais à cela comme à la façon dont un TextView peut se présenter. Si vous spécifiez wrap_content, lorsque vous définissez du texte, TextView peut demander une passe de mise en page et modifier la quantité d'espace qu'il occupe à l'écran. Si vous spécifiez match_parent ou une dimension fixe, TextView ne demandera pas de passage de mise en page car la taille est fixe et la quantité de texte insde ne changera jamais la quantité d'espace occupé. RecyclerView est le même. setHasFixedSize () indique à RV qu'il ne devrait jamais avoir besoin de demander des passes de mise en page en fonction des modifications apportées aux éléments de l'adaptateur.
dangVarmit
1
@dangVarmit belle explication!
howerknea
6

Wen nous avons mis setHasFixedSize(true)sur RecyclerViewce que les moyens de taille recycleur est fixe et n'est pas affectée par le contenu de l' adaptateur. Et dans ce cas, il onLayoutn'est pas appelé sur recycleur lorsque nous mettons à jour les données de l'adaptateur (mais il y a une exception).

Passons à l'exemple:

RecyclerViewa une RecyclerViewDataObserver( trouver l'implemntation par défaut dans ce fichier ) avec plusieurs méthodes, la principale importante est:

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

Cette méthode est appelée si nous fixons setHasFixedSize(true)et mettre à jour les données d'un adaptateur via: notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. Dans ce cas, il n'y a pas d'appels au recycleur onLayout, mais il y a des appels à requestLayoutpour mettre à jour les enfants.

Mais si nous définissons setHasFixedSize(true)et mettons à jour les données d'un adaptateur via, notifyItemChangedil y a un appel à onChangela valeur par défaut du recycleur RecyclerViewDataObserveret aucun appel à triggerUpdateProcessor. Dans ce cas, le recycleur onLayoutest appelé chaque fois que nous définissons setHasFixedSize trueou 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();
    }
}

Comment vérifier par vous-même:

Créer une personnalisation RecyclerViewet remplacer:

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

Définissez la taille du recycleur sur match_parent(en xml). Essayez de mettre à jour les données de l'adaptateur en utilisant replaceDataet replaceOne avec la configuration setHasFixedSize(true), puis 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)
}

Et vérifiez votre journal.

Mon journal:

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

Résumer:

Si nous définissons setHasFixedSize(true)et mettons à jour les données de l'adaptateur en notifiant un observateur d'une autre manière que l'appel notifyDataSetChanged, alors vous avez des performances, car il n'y a pas d'appels à la onLayoutméthode de recyclage .

bitvale
la source
Avez-vous testé avec RecyclerView de la hauteur en utilisant wrap_content ou match_parent?
Lubos Mudrak
6

Si nous avons un RecyclerViewavec match_parentcomme hauteur / largeur , nous devons ajouter setHasFixedSize(true)car la taille de RecyclerViewlui-même ne change pas l'insertion ou la suppression d'éléments.

setHasFixedSize doit être false si nous avons un RecyclerView avec wrap_contentcomme hauteur / largeur puisque chaque élément inséré par l'adaptateur pourrait changer la taille du en Recyclerfonction des éléments insérés / supprimés, donc, la taille du Recyclersera différente à chaque fois que nous ajoutons / supprimons articles.

Pour être plus clair, si nous utilisons

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

On peut utiliser 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"/>

Nous devrions utiliser my_recycler_view.setHasFixedSize(false), cela s'applique si nous utilisons également wrap_contentcomme largeur

Gastón Saillén
la source
3

setHasFixedSize (true) signifie que RecyclerView a des enfants (éléments) qui ont une largeur et une hauteur fixes. Cela permet au RecyclerView de mieux optimiser en déterminant la hauteur et la largeur exactes de la liste entière en fonction de votre adaptateur.

Parc Calvin
la source
5
Ce n'est pas ce que @dangVarmit a suggéré.
strangetimes
5
Trompeur, c'est en fait la taille de la vue Recycler, pas la taille du contenu
Benoit
0

Cela affecte les animations de la vue de recyclage, si c'est false.. les animations d'insertion et de suppression ne s'afficheront pas. alors assurez-vous que c'est trueau cas où vous avez ajouté une animation pour la vue de recyclage.

Alaa AbuZarifa
la source
0

Si la taille du RecyclerView (le RecyclerView lui-même)

... ne dépend pas du contenu de l'adaptateur:

mRecyclerView.setHasFixedSize(true);

... dépend du contenu de l'adaptateur:

mRecyclerView.setHasFixedSize(false);
Alok Singh
la source