Grundlegendes zu RecyclerView setHasFixedSize

136

Ich habe Probleme beim Verstehen setHasFixedSize(). Ich weiß, dass es zur Optimierung verwendet wird, wenn sich die Größe von RecyclerViewaus den Dokumenten nicht ändert.

Was bedeutet das aber? In den meisten Fällen hat ein ListViewfast immer eine feste Größe. In welchen Fällen wäre es keine feste Größe? Bedeutet dies, dass die tatsächliche Fläche, die es auf dem Bildschirm einnimmt, mit dem Inhalt wächst?

SIr Codealot
quelle
Ich fand diese Antwort hilfreich und sehr leicht zu verstehen [StackOverflow - rv.setHasFixedSize (true); ] ( stackoverflow.com/questions/28827597/… )
Mustafa Hasan

Antworten:

115

Eine sehr vereinfachte Version von RecyclerView hat:

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

Dieser Link beschreibt, warum Anrufe requestLayoutteuer sein können. Grundsätzlich kann sich beim Einfügen, Verschieben oder Entfernen von Elementen die Größe (Breite und Höhe) von RecyclerView ändern, und wiederum kann sich die Größe jeder anderen Ansicht in der Ansichtshierarchie ändern. Dies ist besonders problematisch, wenn Elemente häufig hinzugefügt oder entfernt werden.

Vermeiden Sie unnötige Layoutdurchläufe, indem Sie setHasFixedSizeauf true setzen, wenn Sie den Inhalt des Adapters ändern, ohne dessen Höhe oder Breite zu ändern.


Update: Die JavaDoc wurde aktualisiert, um besser zu beschreiben , was die Methode tatsächlich der Fall ist.

RecyclerView kann mehrere Optimierungen durchführen, wenn im Voraus bekannt ist, dass die Größe von RecyclerView nicht vom Adapterinhalt beeinflusst wird. RecyclerView kann seine Größe weiterhin aufgrund anderer Faktoren ändern (z. B. der Größe des übergeordneten Elements). Diese Größenberechnung kann jedoch nicht von der Größe der untergeordneten Elemente oder dem Inhalt des Adapters abhängen (mit Ausnahme der Anzahl der Elemente im Adapter).

Wenn Ihre Verwendung von RecyclerView in diese Kategorie fällt, setzen Sie dies auf {@code true}. Dadurch kann RecyclerView vermeiden, das gesamte Layout ungültig zu machen, wenn sich der Inhalt des Adapters ändert.

@param hasFixedSize true, wenn Adapteränderungen die Größe der RecyclerView nicht beeinflussen können.

LukaCiko
quelle
162
Die Größe von RecyclerView ändert sich jedes Mal, wenn Sie etwas hinzufügen, egal was passiert. SetHasFixedSize stellt (durch Benutzereingabe) sicher, dass diese Größenänderung von RecyclerView konstant ist. Die Höhe (oder Breite) des Elements ändert sich nicht. Jeder hinzugefügte oder entfernte Gegenstand ist der gleiche. Wenn Sie dies nicht einstellen, wird überprüft, ob sich die Größe des Artikels geändert hat und das teuer ist. Nur klarstellen, weil diese Antwort verwirrend ist.
Arnold Balliu
9
@ArnoldB ausgezeichnete Klarstellung. Ich würde es sogar als eigenständige Antwort argumentieren.
young_souvlaki
4
@ArnoldB - Ich bin immer noch verwirrt. Schlagen Sie vor, hasFixedSize auf true zu setzen, wenn die Breiten / Höhen aller Kinder konstant sind? Wenn ja, was ist, wenn die Möglichkeit besteht, dass einige Kinder zur Laufzeit entfernt werden können (ich habe eine Wischfunktion, um die Funktion zu schließen) - ist es in Ordnung, true festzulegen?
Jaguar
1
Ja. Weil sich die Breite und Höhe des Artikels nicht ändert. Es wird nur hinzugefügt oder entfernt. Das Hinzufügen oder Entfernen von Elementen ändert ihre Größe nicht.
Arnold Balliu
3
@ArnoldB Ich denke nicht, dass die Größe (Breite / Höhe) des Artikels hier ein Problem ist. Es wird auch nicht die Größe des Artikels überprüft. Es weist den RecyclerView lediglich an, requestLayoutnach Aktualisierung des dataSet aufzurufen oder nicht.
Kimi Chiu
22

Kann bestätigen, setHasFixedSizebezieht sich auf die RecyclerView selbst und nicht auf die Größe jedes daran angepassten Elements.

Sie können jetzt android:layout_height="wrap_content"eine RecyclerView verwenden, mit der unter anderem ein CollapsingToolbarLayout erkennt , dass es nicht kollabieren sollte, wenn die RecyclerView leer ist. Dies funktioniert nur, wenn Sie es setHasFixedSize(false)in RecylcerView verwenden.

Wenn Sie setHasFixedSize(true)RecyclerView verwenden, funktioniert dieses Verhalten, um zu verhindern, dass das CollapsingToolbarLayout zusammenbricht, nicht, obwohl das RecyclerView tatsächlich leer ist.

Wenn dies setHasFixedSizemit der Größe der Elemente zusammenhängt, sollte dies keine Auswirkungen haben, wenn die RecyclerView keine Elemente enthält.

Kevin
quelle
4
Ich habe gerade eine Erfahrung gemacht, die in die gleiche Richtung weist. Verwenden einer RecyclerView mit einem GridLayoutManager (3 Elemente pro Zeile) und layout_height = wrap_content. Wenn ich auf eine Schaltfläche klicke, die der Liste 3 neue Elemente hinzufügt, wird die Recycler-Ansicht nicht an die neuen Elemente angepasst. Vielmehr behält es die gleiche Größe bei, und die einzige Möglichkeit, die neuen Elemente anzuzeigen, besteht darin, sie zu scrollen. Obwohl die Elemente dieselbe Größe haben, musste ich sie entfernen setHasFixedSize(true), damit sie beim Hinzufügen neuer Elemente erweitert werden.
Mateus Gondim
Ich denke, du hast recht. Aus dem Dokument: hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView.Auch wenn sich die Größe des Elements ändert, können Sie dies auf true setzen.
Kimi Chiu
12

Die ListView hatte eine ähnlich benannte Funktion, die meiner Meinung nach Informationen über die Größe der einzelnen Listenelementhöhen widerspiegelte. In der Dokumentation zu RecyclerView wird ziemlich deutlich angegeben, dass es sich auf die Größe des RecyclerView selbst bezieht, nicht auf die Größe seiner Elemente.

Aus dem RecyclerView-Quellkommentar über der setHasFixedSize () -Methode:

 * 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
quelle
16
Aber wie ist die "Größe" von RecyclerView definiert? Ist es die Größe, die nur auf dem Bildschirm sichtbar ist, oder die volle Größe der RecyclerView, die gleich ist (Summe der Elementhöhen + Polsterung + Abstand)?
Vicky Chijwani
2
In der Tat braucht dies mehr Informationen. Wenn Sie Elemente entfernen und die Recyclingansicht verkleinert wird, wird davon ausgegangen, dass sich die Größe geändert hat?
Henrique de Sousa
4
Ich würde es so sehen, wie sich eine Textansicht selbst gestalten kann. Wenn Sie wrap_content angeben, kann TextView beim Festlegen von Text einen Layoutdurchlauf anfordern und den Platz auf dem Bildschirm ändern. Wenn Sie match_parent oder eine feste Dimension angeben, fordert TextView keinen Layoutdurchlauf an, da die Größe fest ist und die Menge an Text inde niemals den belegten Speicherplatz ändert. RecyclerView ist das gleiche. setHasFixedSize () weist RV darauf hin, dass es niemals erforderlich sein sollte, Layoutdurchläufe aufgrund von Änderungen an den Adapterelementen anzufordern.
DangVarmit
1
@dangVarmit schöne Erklärung!
Howerknea
6

Wen wir setzen setHasFixedSize(true)auf , RecyclerViewdass Mittel Recycler eine feste Größe ist und nicht durch den Adapter Inhalt betroffen. In diesem Fall onLayoutwird der Recycler nicht aufgerufen, wenn die Daten des Adapters aktualisiert werden (es gibt jedoch eine Ausnahme).

Gehen wir zum Beispiel:

RecyclerViewhat eine RecyclerViewDataObserver( Standardimplementierung in dieser Datei finden ) mit mehreren Methoden, die wichtigste ist:

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

Diese Methode wird aufgerufen, wenn wir setHasFixedSize(true)die Daten eines Adapters über Folgendes festlegen und aktualisieren : notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. In diesem Fall gibt es keine Anrufe beim Recycler onLayout, aber Anrufe requestLayoutbeim Aktualisieren von Kindern.

Wenn wir jedoch setHasFixedSize(true)die Daten eines Adapters über einstellen und aktualisieren, notifyItemChangedwird onChangedie Standardeinstellung des Recyclers aufgerufen RecyclerViewDataObserverund keine aufgerufen triggerUpdateProcessor. In diesem Fall wird der Recycler onLayoutimmer dann aufgerufen, wenn wir setHasFixedSize trueoder einstellen 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();
    }
}

So überprüfen Sie selbst:

Benutzerdefiniert erstellen RecyclerViewund überschreiben:

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

Stellen Sie die Größe des Recyclers auf match_parent(in xml). Versuchen Sie, die Daten des Adapters mit replaceDataund replaceOne mit der Einstellung setHasFixedSize(true)und dann zu aktualisieren 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)
}

Und überprüfen Sie Ihr Protokoll.

Mein 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

Zusammenfassen:

Wenn wir die Adapterdaten so einstellen setHasFixedSize(true)und aktualisieren, dass ein Beobachter auf eine andere Weise als durch Aufrufen benachrichtigt wird notifyDataSetChanged, haben Sie eine gewisse Leistung, da dies keine Aufrufe der Recycler- onLayoutMethode sind.

Bitvale
quelle
Haben Sie mit RecyclerView die Höhe mit wrap_content oder match_parent getestet?
Lubos Mudrak
6

Wenn wir ein RecyclerViewmit match_parentals Höhe / Breite haben , sollten wir hinzufügen, setHasFixedSize(true)da die Größe des RecyclerViewselbst das Einfügen oder Löschen von Elementen nicht ändert.

setHasFixedSize sollte falsch sein , wenn wir einen RecyclerView mit haben wrap_contentals Höhe / Breite , da jedes Element durch den Adapter eingeführt könnte die Größe der Änderung Recyclerauf den eingefügten Elementen abhängig / gelöscht, so wird die Größe des Recycleranders sein wird jedes Mal , wenn wir hinzufügen / löschen Artikel.

Um klarer zu sein, wenn wir verwenden

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

Wir können benutzen 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"/>

Wir sollten verwenden my_recycler_view.setHasFixedSize(false), dies gilt, wenn wir auch wrap_contentals Breite verwenden

Gastón Saillén
quelle
3

setHasFixedSize (true) bedeutet, dass RecyclerView untergeordnete Elemente (Elemente) mit fester Breite und Höhe hat. Auf diese Weise kann RecyclerView besser optimieren, indem die genaue Höhe und Breite der gesamten Liste anhand Ihres Adapters ermittelt wird.

Calvin Park
quelle
5
Das hat @dangVarmit nicht vorgeschlagen.
seltsamerweise
5
Irreführend, es ist eigentlich die Größe der Recycler-Ansicht, nicht die Größe des Inhalts
Benoit
0

Dies wirkt sich auf die Animationen der Recycling-Ansicht aus, wenn dies der Fall ist false. Die Animationen zum Einfügen und Entfernen werden nicht angezeigt. Stellen Sie daher sicher, dass trueSie eine Animation für die Recyclingansicht hinzugefügt haben.

Alaa AbuZarifa
quelle
0

Wenn die Größe der RecyclerView (die RecyclerView selbst)

... hängt nicht vom Adapterinhalt ab:

mRecyclerView.setHasFixedSize(true);

... hängt vom Adapterinhalt ab:

mRecyclerView.setHasFixedSize(false);
Alok Singh
quelle