Zrozumienie RecyclerView setHasFixedSize

136

Mam problem ze zrozumieniem setHasFixedSize(). Wiem, że jest używany do optymalizacji, gdy rozmiar RecyclerViewsię nie zmienia, z dokumentów.

Co to jednak oznacza? W większości przypadków ListViewprawie zawsze ma stały rozmiar. W jakich przypadkach nie byłby to stały rozmiar? Czy to oznacza, że ​​rzeczywista nieruchomość, którą zajmuje na ekranie, rośnie wraz z zawartością?

SIr Codealot
źródło
Uważam, że ta odpowiedź jest pomocna i bardzo łatwa do zrozumienia [StackOverflow - rv.setHasFixedSize (true); ] ( stackoverflow.com/questions/28827597/… )
Mustafa Hasan,

Odpowiedzi:

115

Bardzo uproszczona wersja RecyclerView posiada:

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

Ten link opisuje, dlaczego dzwonienie requestLayoutmoże być drogie. Zasadniczo za każdym razem, gdy elementy są wstawiane, przenoszone lub usuwane, rozmiar (szerokość i wysokość) RecyclerView może ulec zmianie, a z kolei rozmiar dowolnego innego widoku w hierarchii widoków może się zmienić. Jest to szczególnie kłopotliwe, jeśli elementy są często dodawane lub usuwane.

Unikaj niepotrzebnych przebiegów układu, ustawiając wartość setHasFixedSizetrue, gdy zmiana zawartości adaptera nie powoduje zmiany jego wysokości ani szerokości.


Aktualizacja: JavaDoc został zaktualizowany, aby lepiej opisać, co faktycznie robi metoda.

RecyclerView może przeprowadzić kilka optymalizacji, jeśli może z góry wiedzieć, że zawartość karty nie ma wpływu na rozmiar RecyclerView. RecyclerView może nadal zmieniać swój rozmiar na podstawie innych czynników (np. Rozmiaru swojego rodzica), ale to obliczenie rozmiaru nie może zależeć od rozmiaru jego elementów podrzędnych lub zawartości jego adaptera (z wyjątkiem liczby elementów w adapterze).

Jeśli Twoje użycie RecyclerView należy do tej kategorii, ustaw ją na {@code true}. Pozwoli to RecyclerView uniknąć unieważnienia całego układu, gdy zmieni się zawartość jego adaptera.

@param hasFixedSize true, jeśli zmiany adaptera nie mogą wpłynąć na rozmiar RecyclerView.

LukaCiko
źródło
162
Rozmiar RecyclerView zmienia się za każdym razem, gdy coś dodajesz, bez względu na wszystko. To, co robi setHasFixedSize, to upewnienie się (na podstawie danych wejściowych użytkownika), że ta zmiana rozmiaru RecyclerView jest stała. Wysokość (lub szerokość) elementu nie ulegnie zmianie. Każdy dodany lub usunięty element będzie taki sam. Jeśli tego nie ustawisz, sprawdzi, czy rozmiar elementu się zmienił i czy jest to drogie. Tylko wyjaśnienie, ponieważ ta odpowiedź jest myląca.
Arnold Balliu
9
@ArnoldB doskonałe wyjaśnienie. Nawet argumentowałbym, że to samodzielna odpowiedź.
young_souvlaki
4
@ArnoldB - nadal jestem zdezorientowany. Czy sugerujesz, że powinniśmy ustawić hasFixedSize na true, jeśli szerokości / wysokości wszystkich elementów podrzędnych są stałe? Jeśli tak, co się stanie, jeśli istnieje możliwość, że niektóre dzieci mogą zostać usunięte w czasie wykonywania (mam przesunięcie palcem, aby odrzucić funkcję) - czy można ustawić wartość true?
Jaguar
1
Tak. Ponieważ szerokość i wysokość elementu się nie zmieniają. Jest tylko dodawany lub usuwany. Dodawanie lub usuwanie elementów nie zmienia ich rozmiaru.
Arnold Balliu
3
@ArnoldB Nie sądzę, żeby rozmiar (szerokość / wysokość) przedmiotu był tutaj problemem. Nie będzie też sprawdzać rozmiaru przedmiotu. Po prostu informuje RecyclerView, aby wywołać requestLayoutlub nie po zaktualizowaniu dataSet.
Kimi Chiu,
22

Potwierdza, że setHasFixedSizeodnosi się do samego RecyclerView, a nie do rozmiaru każdego dostosowanego do niego elementu.

Możesz teraz używać android:layout_height="wrap_content"w RecyclerView, co między innymi pozwala CollapsingToolbarLayout wiedzieć, że nie powinno się zwijać, gdy RecyclerView jest pusty. Działa to tylko wtedy, gdy używasz setHasFixedSize(false)w RecylcerView.

Jeśli używasz setHasFixedSize(true)w RecyclerView, to zachowanie, aby zapobiec zwijaniu CollapsingToolbarLayout, nie działa, nawet jeśli RecyclerView jest rzeczywiście pusty.

Jeśli setHasFixedSizebył związany z rozmiarem elementów, nie powinien mieć żadnego wpływu, gdy RecyclerView nie ma żadnych elementów.

Kevin
źródło
4
Właśnie miałem doświadczenie, które wskazuje w tym samym kierunku. Korzystanie z RecyclerView z GridLayoutManager (3 elementy na wiersz) i layout_height = wrap_content. Kiedy klikam przycisk, który dodaje 3 nowe pozycje do listy, widok recyklera nie rozszerza się, aby pasował do nowych pozycji. Raczej zachowuje ten sam rozmiar, a jedynym sposobem, aby zobaczyć nowe elementy, jest jego przewijanie. Mimo że elementy mają ten sam rozmiar, musiałem je usunąć, setHasFixedSize(true)aby rozszerzyć się po dodaniu nowych elementów.
Mateus Gondim
Myślę, że masz rację. Z dokumentu, hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView.więc nawet jeśli rozmiar elementu się zmieni, nadal możesz ustawić to na true.
Kimi Chiu,
12

ListView miał podobną nazwaną funkcję, która moim zdaniem odzwierciedlała informacje o rozmiarze poszczególnych wysokości elementów listy. Dokumentacja RecyclerView dość wyraźnie stwierdza, że ​​odnosi się do rozmiaru samego RecyclerView, a nie do rozmiaru jego elementów.

Z komentarza źródła RecyclerView powyżej metody 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
źródło
16
Ale jak definiuje się „rozmiar” RecyclerView? Czy jest to rozmiar widoczny tylko na ekranie, czy pełny rozmiar RecyclerView, który jest równy (suma wysokości elementów + dopełnienie + odstępy)?
Vicky Chijwani
2
Rzeczywiście, to wymaga więcej informacji. Jeśli usuniesz elementy, a widok recyklingu zmniejszy się, czy uważa się, że zmienił się rozmiar?
Henrique de Sousa
4
Pomyślałbym o tym jak o tym, jak może się rozłożyć TextView. Jeśli określisz wrap_content, po ustawieniu tekstu TextView może zażądać przejścia układu i zmienić ilość zajmowanego miejsca na ekranie. Jeśli określisz match_parent lub stały wymiar, TextView nie zażąda przejścia układu, ponieważ rozmiar jest stały, a ilość wprowadzanego tekstu nigdy nie zmieni ilości zajmowanego miejsca. RecyclerView jest taki sam. setHasFixedSize () podpowiada RV, że nigdy nie powinien żądać przejść układu na podstawie zmian w elementach adaptera.
dangVarmit,
1
@dangVarmit ładne wyjaśnienie!
howerknea
6

Wen stawiamy setHasFixedSize(true)na RecyclerViewtym, że środki recyklingowa na wielkość jest stała i nie zależy od zawartości karty. I w tym przypadku onLayoutnie jest wywoływany recykler, gdy aktualizujemy dane adaptera (ale jest wyjątek).

Przejdźmy do przykładu:

RecyclerViewma RecyclerViewDataObserver( znajdź domyślną implementację w tym pliku ) z kilkoma metodami, najważniejsze to:

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

Metoda ta nazywana jest jeśli mamy ustawiony setHasFixedSize(true)i aktualizować dane za pośrednictwem adaptera: notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. W tym przypadku nie ma wywołań do recyklera onLayout, ale są wezwania do requestLayoutaktualizacji child.

Ale jeśli ustawimy setHasFixedSize(true)i zaktualizujemy dane adaptera za pośrednictwem, notifyItemChangedzostanie onChangewywołane domyślne RecyclerViewDataObserverwywołanie recyklera i nie będzie wywołań triggerUpdateProcessor. W tym przypadku recykler onLayoutjest wywoływany za każdym razem, gdy ustawimy setHasFixedSize truelub 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();
    }
}

Jak sprawdzić samodzielnie:

Utwórz własne RecyclerViewi zastąp:

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

Ustaw rozmiar recyklera na match_parent(w xml). Spróbuj zaktualizować dane adaptera za pomocą replaceDatai replaceOne z ustawieniem, setHasFixedSize(true)a następnie 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)
}

I sprawdź swój dziennik.

Mój dziennik:

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

Podsumować:

Jeśli ustawimy setHasFixedSize(true)i zaktualizujemy dane adaptera, powiadamiając obserwatora w inny sposób niż wywołanie notifyDataSetChanged, masz pewną wydajność, ponieważ nie ma wywołań onLayoutmetody recyklera .

bitvale
źródło
Czy testowałeś wysokość z RecyclerView przy użyciu wrap_content lub match_parent?
Lubos Mudrak
6

Jeśli mamy RecyclerViewz match_parentjak wysokość / szerokość , dodajmy setHasFixedSize(true)od rozmiaru RecyclerViewsobie nie zmienia wstawianie lub usuwanie elementów do niego.

setHasFixedSize powinny być fałszywe, jeśli mamy RecyclerView z wrap_contentjak wysokość / szerokość od każdy element wstawiony przez adapter może zmienić wielkość Recyclerw zależności od elementów wstawionych / usunięte, więc wielkość Recyclerbędzie inna za każdym razem Dodaj / Usuń przedmiotów.

Żeby było jaśniej, jeśli używamy

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

Możemy użyć 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"/>

Powinniśmy użyć my_recycler_view.setHasFixedSize(false), dotyczy to również użycia wrap_contentjako szerokości

Gastón Saillén
źródło
3

setHasFixedSize (true) oznacza, że ​​RecyclerView ma elementy podrzędne (elementy), które mają stałą szerokość i wysokość. Pozwala to RecyclerView na lepszą optymalizację, określając dokładną wysokość i szerokość całej listy w oparciu o adapter.

Calvin Park
źródło
5
Nie to zasugerował @dangVarmit.
strangetimes
5
Mylące, w rzeczywistości jest to rozmiar widoku Recycler, a nie rozmiar treści
Benoit
0

Wpływa na animacje widoku recyklingu, jeśli tak jest false... animacje wstawiania i usuwania nie będą wyświetlane. więc upewnij się, że jest to na truewypadek, gdybyś dodał animację do widoku recyklingu.

Alaa AbuZarifa
źródło
0

Jeśli rozmiar RecyclerView (sam RecyclerView)

... nie zależy od zawartości adaptera:

mRecyclerView.setHasFixedSize(true);

... zależy od zawartości adaptera:

mRecyclerView.setHasFixedSize(false);
Alok Singh
źródło