wake-up-neo.com

RecyclerView setHasFixedSize verstehen

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

Was heißt das aber? In den meisten Fällen hat eine ListView fast immer eine feste Größe. In welchen Fällen wäre es keine feste Größe? Bedeutet das, dass die tatsächliche Immobilie, die sie auf dem Bildschirm belegt, mit dem Inhalt wächst?

102
SIr Codealot

Eine sehr vereinfachte Version von RecyclerView hat:

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

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

Vermeiden Sie unnötige Layoutdurchläufe, indem Sie setHasFixedSize auf true setzen, wenn der Inhalt des Adapters nicht seine Höhe oder Breite ändert.


Update: Das JavaDoc wurde aktualisiert, um besser zu beschreiben, was die Methode tatsächlich bewirkt.

RecyclerView kann mehrere Optimierungen durchführen, wenn dies in .__ bekannt ist. voraus, dass die Größe von RecyclerView nicht durch den Adapter beeinflusst wird Inhalt. RecyclerView kann seine Größe weiterhin basierend auf anderen .__ ändern. Faktoren (z. B. die Größe des übergeordneten Elements), diese Größenberechnung kann jedoch nicht abhängig von der Größe der untergeordneten Elemente oder vom Inhalt des Adapters (außer der Anzahl der Elemente im Adapter). 

Wenn Sie RecyclerView verwenden fällt in diese Kategorie, setzen Sie diese auf {@code true}. Es wird erlaubt RecyclerView, um zu vermeiden, dass das gesamte Layout ungültig wird, wenn der Adapter Inhalte ändern sich.

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

84
LukaCiko

Kann bestätigen, dass setHasFixedSize sich auf die RecyclerView selbst bezieht und nicht auf die Größe jedes Elements, das an sie angepasst ist.

Sie können Android:layout_height="wrap_content" jetzt für eine RecyclerView verwenden, wodurch einem CollapsingToolbarLayout unter anderem ermöglicht wird, dass es nicht kollabiert, wenn die RecyclerView leer ist. Dies funktioniert nur, wenn Sie setHasFixedSize(false) in RecylcerView verwenden. 

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

Wenn sich setHasFixedSize auf die Größe der Elemente bezieht, sollte dies keine Auswirkungen haben, wenn die RecyclerView keine Elemente enthält.

17
Kevin

Die ListView hatte eine ähnliche benannte Funktion, die Informationen über die Größe der einzelnen Listenelementhöhen widerspiegelt. In der Dokumentation zu RecyclerView wird ziemlich eindeutig darauf hingewiesen, 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.
12
dangVarmit

setHasFixedSize (true) bedeutet, dass der RecyclerView untergeordnete Elemente (Elemente) mit fester Breite und Höhe hat. Dadurch kann RecyclerView besser optimiert werden, indem die genaue Höhe und Breite der gesamten Liste basierend auf Ihrem Adapter ermittelt wird.

4
Calvin Park

Wenn wir setHasFixedSize(true) auf RecyclerView setzen, bedeutet dies, dass die Größe des Recyclers festgelegt ist und nicht vom Adapterinhalt beeinflusst wird. In diesem Fall wird onLayout nicht beim Recycler aufgerufen, wenn wir die Daten des Adaptrers aktualisieren (es gibt jedoch eine Ausnahme).

Gehen wir zum Beispiel:

RecyclerView hat eine RecyclerViewDataObserver ( find Standardimplementierung in dieser Datei ) mit mehreren Methoden. Das 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) setzen und die Daten eines Adapters aktualisieren: notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. In diesem Fall gibt es keine Aufrufe der onLayout des Recyclers, aber requestLayout Aufrufe zum Aktualisieren von Childs.

Wenn wir jedoch setHasFixedSize(true) setzen und die Daten eines Adapters über notifyItemChanged aktualisieren, gibt es einen Aufruf an onChange der Standardeinstellung RecyclerViewDataObserver des Verwerters und keine Aufrufe an triggerUpdateProcessor. In diesem Fall wird der Recycler onLayout aufgerufen, wenn wir setHasFixedSizetrue oder false setzen.

// 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 prüfen Sie selbst:

Benutzerdefinierte RecyclerView erstellen und ü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)
}

Legen Sie die Größe des Recyclers auf match_parent (in xml) fest. Aktualisieren Sie die Daten des Adapters mit replaceData und replaceOne mit der Einstellung setHasFixedSize(true) und dann 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üfe dein Logbuch.

Mein Protokoll:

// 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 Sie setHasFixedSize(true) setzen und die Daten des Adapters aktualisieren, indem Sie einen Beobachter auf eine andere Weise als den Aufruf von notifyDataSetChanged benachrichtigen, haben Sie etwas Leistung, da dies keine Aufrufe der onLayout-Methode des Recyclers ist.

2
bitvale

Dies hat Auswirkungen auf die Animationen des Recyclerüberblicks, wenn dies false ist. Die Animationen zum Einfügen und Entfernen werden nicht angezeigt. Stellen Sie daher sicher, dass es true ist, falls Sie eine Animation für das Recycling-Fenster hinzugefügt haben.

0
Alaa AbuZarifa

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);
0
Alok Singh