Comprensione di RecyclerView setHasFixedSize


136

Ho qualche problema a capire setHasFixedSize(). So che viene utilizzato per l'ottimizzazione quando la dimensione di RecyclerViewnon cambia, dai documenti.

Cosa significa questo però? Nella maggior parte dei casi, ListViewquasi sempre ha una dimensione fissa. In quali casi non sarebbe una dimensione fissa? Significa che il reale immobile che occupa sullo schermo cresce con il contenuto?



Ho trovato questa risposta utile e molto facile da capire [StackOverflow - rv.setHasFixedSize (true); ] ( stackoverflow.com/questions/28827597/… )
Mustafa Hasan

Risposte:


115

Una versione molto semplificata di RecyclerView ha:

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

Questo link descrive perché chiamare requestLayoutpotrebbe essere costoso. Fondamentalmente ogni volta che gli elementi vengono inseriti, spostati o rimossi, le dimensioni (larghezza e altezza) di RecyclerView potrebbero cambiare e, a sua volta, le dimensioni di qualsiasi altra vista nella gerarchia della vista potrebbero cambiare. Ciò è particolarmente problematico se gli articoli vengono aggiunti o rimossi frequentemente.

Evita passaggi di layout non necessari impostando setHasFixedSizesu true quando la modifica del contenuto dell'adattatore non modifica l'altezza o la larghezza.


Aggiornamento: JavaDoc è stato aggiornato per descrivere meglio cosa fa effettivamente il metodo.

RecyclerView può eseguire diverse ottimizzazioni se può sapere in anticipo che le dimensioni di RecyclerView non sono influenzate dal contenuto dell'adattatore. RecyclerView può comunque modificare le sue dimensioni in base ad altri fattori (ad es. Le dimensioni dei suoi genitori) ma questo calcolo delle dimensioni non può dipendere dalle dimensioni dei suoi figli o dal contenuto del suo adattatore (tranne il numero di articoli nell'adattatore).

Se l'utilizzo di RecyclerView rientra in questa categoria, impostarlo su {@code true}. Consentirà a RecyclerView di evitare di invalidare l'intero layout quando cambiano i contenuti dell'adattatore.

@param hasFixedSize true se le modifiche dell'adattatore non possono influire sulla dimensione di RecyclerView.


162
Le dimensioni di RecyclerView cambiano ogni volta che aggiungi qualcosa, qualunque cosa accada. Ciò che setHasFixedSize fa è che (per input dell'utente) si assicura che questo cambiamento di dimensione di RecyclerView sia costante. L'altezza (o larghezza) dell'articolo non cambierà. Ogni elemento aggiunto o rimosso sarà lo stesso. Se non lo imposti, verificherà se le dimensioni dell'articolo sono cambiate ed è costoso. Chiarire solo perché questa risposta è confusa.
Arnold Balliu,

9
@ArnoldB ottimo chiarimento. Lo direi anche come risposta autonoma.
young_souvlaki,

4
@ArnoldB - Sono ancora confuso. Stai suggerendo che dovremmo impostare hasFixedSize su true se le larghezze / altezze di tutti i bambini sono costanti? In caso affermativo, cosa succede se esiste la possibilità che alcuni bambini possano essere rimossi in fase di esecuzione (ho un colpo per chiudere la funzione) - va bene impostare true?
Jaguar,

1
Sì. Perché la larghezza e l'altezza dell'articolo non cambiano. È appena stato aggiunto o rimosso. L'aggiunta o la rimozione di elementi non cambia le loro dimensioni.
Arnold Balliu il

3
@ArnoldB Non credo che la dimensione (larghezza / altezza) dell'articolo sia un problema qui. Non controllerà nemmeno le dimensioni dell'articolo. Indica semplicemente a RecyclerView di chiamare requestLayouto meno dopo aver aggiornato il DataSet.
Kimi Chiu,

22

Può confermare si setHasFixedSizeriferisce a RecyclerView stesso e non alle dimensioni di ciascun articolo adattato ad esso.

Ora è possibile utilizzare android:layout_height="wrap_content"su RecyclerView, che, tra le altre cose, consente a CollapsingToolbarLayout di sapere che non dovrebbe collassare quando RecyclerView è vuoto. Funziona solo quando si utilizza setHasFixedSize(false)RecylcerView.

Se si utilizza setHasFixedSize(true)su RecyclerView, questo comportamento per impedire il collasso di CollapsingToolbarLayout non funziona, anche se RecyclerView è effettivamente vuoto.

Se setHasFixedSizefosse correlato alla dimensione degli articoli, non dovrebbe avere alcun effetto quando RecyclerView non ha articoli.


4
Ho appena avuto un'esperienza che punta nella stessa direzione. Utilizzo di RecyclerView con GridLayoutManager (3 articoli per riga) e layout_height = wrap_content. Quando faccio clic su un pulsante che aggiunge 3 nuovi elementi all'elenco, la vista del riciclatore non si espande per adattarsi ai nuovi elementi. Piuttosto, mantiene le stesse dimensioni e l'unico modo per vedere i nuovi elementi è farlo scorrere. Anche se gli articoli hanno le stesse dimensioni, ho dovuto rimuoverli setHasFixedSize(true)per espanderli quando vengono aggiunti nuovi elementi.
Mateus Gondim,

Penso tu abbia ragione. Dal documento, hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView.quindi anche se le dimensioni dell'articolo cambieranno, puoi comunque impostarlo su true.
Kimi Chiu,

12

ListView aveva una funzione con nome simile che penso riflettesse informazioni sulla dimensione delle singole altezze degli elementi dell'elenco. La documentazione per RecyclerView afferma chiaramente che si riferisce alla dimensione del RecyclerView stesso, non alla dimensione dei suoi articoli.

Dal commento di origine RecyclerView sopra il metodo 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.

16
Ma come vengono definite le "dimensioni" di RecyclerView? È la dimensione visibile solo sullo schermo o l'intera dimensione di RecyclerView, che è uguale (somma delle altezze degli oggetti + imbottitura + spaziatura)?
Vicky Chijwani,

2
In effetti, questo richiede maggiori informazioni. Se si rimuovono gli articoli e il recyclerview si restringe, si ritiene che le dimensioni siano state modificate?
Henrique de Sousa,

4
Ci penserei come come un TextView può stendersi da solo. Se si specifica wrap_content, quando si imposta il testo TextView può richiedere un passaggio di layout e modificare la quantità di spazio che occupa sullo schermo. Se si specifica match_parent o una dimensione fissa, TextView non richiederà un passaggio di layout poiché la dimensione è fissa e la quantità di insde di testo non cambierà mai la quantità di spazio occupato. RecyclerView è lo stesso. setHasFixedSize () suggerisce a RV che non dovrebbe mai richiedere passaggi di layout in base alle modifiche agli elementi dell'adattatore.
dangVarmit,

1
@dangVarmit bella spiegazione!
howerknea,

6

Se impostato setHasFixedSize(true), RecyclerViewsignifica che le dimensioni del riciclatore sono fisse e non sono influenzate dal contenuto dell'adattatore. E in questo caso onLayoutnon viene chiamato il riciclatore quando aggiorniamo i dati dell'adattatore (ma c'è un'eccezione).

Andiamo all'esempio:

RecyclerViewha un RecyclerViewDataObserver( trova implementazione predefinita in questo file ) con diversi metodi, il principale importante è:

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

Questo metodo viene chiamato se abbiamo impostato setHasFixedSize(true)e aggiornare i dati di un adattatore tramite: notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. In questo caso non ci sono chiamate al riciclatore onLayout, ma ci sono chiamate requestLayoutper l'aggiornamento dei bambini.

Ma se impostiamo setHasFixedSize(true)e aggiorniamo i dati di un adattatore tramite notifyItemChangedallora c'è una chiamata al onChangevalore predefinito del riciclatore RecyclerViewDataObservere nessuna chiamata a triggerUpdateProcessor. In questo caso il riciclatore onLayoutviene chiamato ogni volta che impostiamo 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();
    }
}

Come controllare da soli:

Crea personalizzato RecyclerViewe sostituisci:

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

Impostare la dimensione del riciclatore su match_parent(in xml). Prova ad aggiornare i dati dell'adattatore utilizzando replaceDatae replaceOne con l'impostazione setHasFixedSize(true)e quindi 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)
}

E controlla il tuo registro.

Il mio 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

Ricapitolare:

Se impostiamo setHasFixedSize(true)e aggiorniamo i dati dell'adattatore con la notifica a un osservatore in qualche modo diverso dalla chiamata notifyDataSetChanged, allora hai delle prestazioni, perché non si tratta di chiamate al onLayoutmetodo del riciclatore .


Hai provato con RecyclerView di altezza usando wrap_content o match_parent?
Lubos Mudrak,

6

Se abbiamo un RecyclerViewcon match_parentcome altezza / larghezza , dovremmo aggiungere setHasFixedSize(true)poiché la dimensione dello RecyclerViewstesso non cambia inserendo o eliminando elementi in esso.

setHasFixedSize dovrebbe essere falsa se abbiamo un RecyclerView con wrap_contentcome altezza / larghezza in quanto ogni elemento inserito dalla scheda potrebbe cambiare la dimensione della Recyclerseconda delle voci inseriti / cancellati, così, la dimensione del Recyclersarà diverso ogni volta che aggiungiamo / delete elementi.

Per essere più chiari, se usiamo

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

Possiamo usare 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"/>

Dovremmo usare my_recycler_view.setHasFixedSize(false), questo vale se usiamo anche wrap_contentcome larghezza


3

setHasFixedSize (true) significa che RecyclerView ha figli (elementi) con larghezza e altezza fisse. Ciò consente a RecyclerView di ottimizzare meglio individuando l'altezza e la larghezza esatte dell'intero elenco in base all'adattatore.


5
Non è quello che ha suggerito @dangVarmit.
Strangetimes,

5
Ingannevole, in realtà è la dimensione della vista di Recycler, non la dimensione del contenuto
Benoit

0

Colpisce le animazioni del recyclerview, se è false... l'inserimento e la rimozione delle animazioni non verranno visualizzate. quindi assicurati che sia stata trueaggiunta un'animazione per il recyclerview.


0

Se la dimensione di RecyclerView (RecyclerView stesso)

... non dipende dal contenuto dell'adattatore:

mRecyclerView.setHasFixedSize(true);

... dipende dal contenuto dell'adattatore:

mRecyclerView.setHasFixedSize(false);
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.