web-dev-qa-db-fra.com

Comprendre RecyclerView setHasFixedSize

J'ai du mal à comprendre setHasFixedSize(). Je sais qu'il est utilisé pour l'optimisation lorsque la taille de RecyclerView ne change pas, à partir de la documentation.

Qu'est-ce que cela signifie cependant? Dans la plupart des cas courants, une ListView a presque toujours une taille fixe. Dans quels cas ne serait-ce pas une taille fixe? Cela signifie-t-il que les biens immobiliers réels qu'il occupe à l'écran augmentent avec le contenu?

102
SIr Codealot

Une version simplifiée de {très} _ de RecyclerView comprend:

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

Ce lien décrit pourquoi appeler requestLayout peut être coûteux. En gros, 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 peut changer. Cela est particulièrement gênant si des éléments sont ajoutés ou supprimés fréquemment. 

Évitez les passes de présentation inutiles en définissant setHasFixedSize sur true lorsque le contenu de l'adaptateur ne change pas en hauteur ni en largeur.


Update: JavaDoc a été mis à jour pour mieux décrire le fonctionnement réel de la méthode.

RecyclerView peut effectuer plusieurs optimisations s’il peut savoir dans indiquez que la taille de RecyclerView n'est pas affectée par l'adaptateur Contenu. RecyclerView peut toujours changer sa taille en fonction des autres facteurs (par exemple la taille de son parent) mais ce calcul de taille ne peut pas. dépend de la taille de ses enfants ou du contenu de son adaptateur (sauf le nombre d’éléments de l’adaptateur). 

Si vous utilisez RecyclerView tombe dans cette catégorie, définissez ceci sur {@code true}. Cela permettra RecyclerView pour éviter d'invalider la mise en page entière lorsque son adaptateur le contenu change.

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

84
LukaCiko

Peut confirmer que setHasFixedSize concerne le RecyclerView lui-même, et non la taille de chaque élément adapté à celui-ci.

Vous pouvez maintenant utiliser Android:layout_height="wrap_content" sur un RecyclerView, ce qui permet entre autres à un CollapsingToolbarLayout de savoir qu’il ne doit pas s’effondrer lorsque le RecyclerView est vide. Cela ne fonctionne que lorsque vous utilisez setHasFixedSize(false) sur RecylcerView. 

Si vous utilisez setHasFixedSize(true) sur RecyclerView, ce comportement visant à empêcher la réduction de CollapsingToolbarLayout ne fonctionne pas, même si RecyclerView est bien vide. 

Si setHasFixedSize était lié à la taille des éléments, cela n'aurait aucun effet si RecyclerView ne contenait aucun élément.

17
Kevin

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

Dans le commentaire source RecyclerView situé 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.
12
dangVarmit

setHasFixedSize (true) signifie que RecyclerView a des enfants (éléments) de largeur et de hauteur fixes. Cela permet d'optimiser l'optimisation de RecyclerView en déterminant la hauteur et la largeur exactes de la liste complète en fonction de votre adaptateur.

4
Calvin Park

Lorsque nous avons défini setHasFixedSize(true) sur RecyclerView, cela signifie que la taille du recycleur est fixe et n'est pas affectée par le contenu de l'adaptateur. Et dans ce cas, onLayout n'est pas appelé sur le recycleur lorsque nous mettons à jour les données de l'adaptateur (mais il existe une exception).

Allons à l'exemple:

RecyclerView a une RecyclerViewDataObserver ( recherche l'implémentation par défaut dans ce fichier ) avec plusieurs méthodes, la principale étant:

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 définissons setHasFixedSize(true) et mettons à jour les données d'un adaptateur via: notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. Dans ce cas, il n'y a aucun appel à la variable onLayout du recycleur, mais à requestLayout pour la mise à jour des enfants.

Mais si nous définissons setHasFixedSize(true) et mettons à jour les données d'un adaptateur via notifyItemChanged, un appel à onChange de la valeur par défaut du recycleur RecyclerViewDataObserver et aucun appel à triggerUpdateProcessor. Dans ce cas, le recycleur onLayout est appelé chaque fois que nous définissons setHasFixedSizetrue ou 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éez une RecyclerView personnalisée et remplacez:

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 replaceData et replaceOne avec la définition de 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 log:

// 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 d'appeler notifyDataSetChanged, vous obtiendrez certaines performances car il n'y a pas d'appels à la méthode recycler onLayout.

2
bitvale

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 true au cas où vous auriez ajouté une animation pour le recyclage.

0
Alaa AbuZarifa

Si la taille de 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);
0
Alok Singh