Розуміння набору RecyclerViewHasFixedSize


136

У мене є проблеми з розумінням setHasFixedSize(). Я знаю, що він використовується для оптимізації, коли розмір RecyclerViewне змінюється, від документів.

Що це означає, хоча? У більшості випадків ListViewмайже завжди має фіксований розмір. У яких випадках це був би не фіксований розмір? Чи означає це, що фактична нерухомість, яку вона займає на екрані, зростає зі змістом?



Я вважав, що ця відповідь є корисною і її дуже легко зрозуміти [StackOverflow - rv.setHasFixedSize (вірно); ] ( stackoverflow.com/questions/28827597/… )
Мустафа Хасан

Відповіді:


115

Дуже спрощена версія RecyclerView має:

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

Це посилання описує, чому дзвінки requestLayoutможуть бути дорогими. В основному всякий раз, коли елементи вставляються, переміщуються або видаляються, розмір (ширина і висота) RecyclerView може змінюватися, а в свою чергу розмір будь-якого іншого виду в ієрархії подання може змінюватися. Це особливо клопітно, якщо елементи додаються або вилучаються часто.

Уникайте зайвих проходів макета, встановивши setHasFixedSizeзначення true, коли зміна вмісту адаптера не змінює його висоту чи ширину.


Оновлення: JavaDoc було оновлено, щоб краще описати, що метод насправді робить.

RecyclerView може виконати кілька оптимізацій, якщо заздалегідь знає, що на вміст адаптера не впливає розмір RecyclerView. RecyclerView все ще може змінювати свій розмір на основі інших факторів (наприклад, розмір батьків), але цей розрахунок розміру не може залежати від розміру його дітей або вмісту його адаптера (крім кількості елементів у адаптері).

Якщо використання RecyclerView підпадає під цю категорію, встановіть це на {@code true}. Це дозволить RecyclerView уникати недійсності всього макета, коли зміна його вмісту адаптера.

@param hasFixedSize true, якщо зміни адаптера не можуть вплинути на розмір RecyclerView.


162
Розмір RecyclerView змінюється щоразу, коли ви додаєте щось незалежно від того, що. Що робить setHasFixedSize, це те, що він впевнений (за допомогою введення користувача), що ця зміна розміру RecyclerView є постійною. Висота (або ширина) елемента не зміниться. Кожен доданий або видалений елемент буде однаковим. Якщо ви цього не встановите, він перевірить, чи змінився розмір предмета, і це дорого. Просто уточнення, тому що ця відповідь є заплутаною.
Арнольд Балліу

9
@ArnoldB відмінне уточнення. Я б навіть стверджував це як окрему відповідь.
young_souvlaki

4
@ArnoldB - я все ще розгублений. Ви пропонуєте встановити hasFixedSize на істинне, якщо ширина / висота всіх дітей є постійними? Якщо так, що робити, якщо є можливість, щоб деяких дітей можна було видалити під час виконання (у мене є можливість провести пальцем для відхилення) - чи правильно це встановити так?
Ягуар

1
Так. Тому що ширина і висота предмета не змінюються. Він просто додається або видаляється. Додавання або видалення елементів не змінює їх розмір.
Арнольд Балліу

3
@ArnoldB Я не думаю, що розмір (ширина / висота) елемента є проблемою тут. Він також не перевірить розмір товару. Він просто говорить RecyclerView зателефонувати requestLayoutчи ні після оновлення набору даних.
Кімі Чіу

22

Можна підтвердити, що setHasFixedSizeстосується самого RecyclerView, а не розміру кожного адаптованого до нього елемента.

Тепер ви можете використовувати android:layout_height="wrap_content"RecyclerView, який, серед іншого, дозволяє CollapsingToolbarLayout знати, що він не повинен руйнуватися, коли RecyclerView порожній. Це працює лише при використанні setHasFixedSize(false)в RecylcerView.

Якщо ви використовуєте setHasFixedSize(true) в RecyclerView, така поведінка для запобігання згортання CollapsingToolbarLayout не працює, навіть якщо RecyclerView справді порожній.

Якщо це setHasFixedSizeбуло пов'язано з розміром елементів, воно не повинно мати жодного ефекту, коли у RecyclerView немає елементів.


4
Я щойно мав досвід, який вказує на той самий напрямок. Використання RecyclerView з GridLayoutManager (3 елементи в ряд) та layout_height = wrap_content. Коли я натискаю кнопку, яка додає 3 нові елементи до списку, подання переробника не розширюється, щоб відповідати новим. Швидше, він підтримує однаковий розмір, і єдиний спосіб побачити нові елементи - це прокрутка. Незважаючи на те, що елементи мають однаковий розмір, мені довелося видалити, setHasFixedSize(true)щоб розширити його, коли нові елементи додаються.
Матеус Гондим

Я думаю, ти маєш рацію. З документа, hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView.тож навіть якщо розмір елемента зміниться, ви все одно можете встановити це значення true.
Кімі Чіу

12

У ListView була подібна названа функція, яка, на мою думку, відображала інформацію про розміри висоти окремих елементів списку. У документації для RecyclerView досить чітко зазначено, що вона посилається на розмір самого RecyclerView, а не на розмір елементів.

З коментаря джерела RecyclerView вище методу 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
Але як визначається "розмір" RecyclerView? Це розмір, видимий лише на екрані, або повний розмір RecyclerView, який дорівнює (сума висоти елементів + ​​прокладка + інтервал)?
Vicky Chijwani

2
Дійсно, для цього потрібно більше інформації. Якщо ви виймаєте елементи, а переробник перегляду зменшується, чи вважається він зміненим розміром?
Анріке де Суса

4
Я б подумав про це як про те, як TextView може викласти себе. Якщо ви вказали wrap_content, тоді, коли ви встановлюєте текст, TextView може запитати пропуск макета і змінити кількість місця, яке він займає на екрані. Якщо ви вказали match_parent або фіксований розмір, TextView не буде вимагати пропуску макета, оскільки розмір фіксований, а кількість insde тексту ніколи не змінить кількість займаного місця. RecyclerView те саме. setHasFixedSize () натякає на RV, він ніколи не повинен вимагати пропуски макета на основі змін до елементів адаптера.
dangVarmit

1
@dangVarmit приємне пояснення!
howerknea

6

Вень ми встановили setHasFixedSize(true)на RecyclerViewце означає , що переробник - х розмір фіксований і не залежить від вмісту адаптера. І в цьому випадку onLayoutне вимагається використання рециркулятора, коли ми оновлюємо дані адаптера (але є виняток).

Перейдемо до прикладу:

RecyclerViewмає RecyclerViewDataObserver( знайдіть реалізацію за замовчуванням у цьому файлі ) кількома методами, головне важливе:

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

Цей метод викликається , якщо ми встановлюємо setHasFixedSize(true)і оновлювати дані закріпних за допомогою: notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved or notifyItemRangeMoved. У цьому випадку немає дзвінків на переробник onLayout, але є заклики до requestLayoutоновлення дітей.

Але якщо ми встановимо setHasFixedSize(true)та оновимо дані адаптера за допомогою notifyItemChangedцього виклику onChangeза замовчуванням утилізатора RecyclerViewDataObserverі не дзвонить triggerUpdateProcessor. У цьому випадку переробник onLayoutвикликається, коли ми встановимо setHasFixedSize trueабо 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();
    }
}

Як перевірити самостійно:

Створіть власні RecyclerViewта змінити:

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

Встановіть розмір рециркулятора на match_parent(у xml). Спробуйте оновити дані адаптера за допомогою replaceDataта за replaceOne допомогою сеансу, setHasFixedSize(true)а потім 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)
}

І перевірте свій журнал.

Мій журнал:

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

Підсумуйте:

Якщо ми встановлюємо setHasFixedSize(true)та оновлюємо дані адаптера, повідомляючи спостерігача іншим способом, ніж дзвінки notifyDataSetChanged, то у вас є деяка ефективність, оскільки це не викликає onLayoutметод переробки .


Ви протестували з RecyclerView висоти за допомогою wrap_content або match_parent?
Любош Мудрак

6

Якщо ми маємо RecyclerViewз , match_parentяк висота / ширина , ми повинні додати , setHasFixedSize(true)так як розмір RecyclerViewсам по собі не змінює вставки або видалення елементів в нього.

setHasFixedSize має бути хибним , якщо ми маємо RecyclerView з , wrap_contentяк висота / ширина , так як кожен елемент вставляється адаптер може змінити розмір самого в Recyclerзалежності від елементів , вставлених / вилучений, тому розмір Recyclerбуде відрізнятися кожен раз , коли ми додати / видалити предметів.

Щоб бути більш зрозумілим, якщо ми використовуємо

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

Ми можемо використовувати 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"/>

Ми повинні використовувати my_recycler_view.setHasFixedSize(false), це стосується, якщо ми також використовуємо wrap_contentяк ширину


3

setHasFixedSize (true) означає, що RecyclerView має дітей (елементів), які мають фіксовану ширину та висоту. Це дозволяє RecyclerView краще оптимізувати, з'ясовуючи точну висоту та ширину всього списку на основі вашого адаптера.


5
Це не те, що запропонував @dangVarmit.
задухи

5
Оманливий, це насправді розмір перегляду Recycler, а не розмір вмісту
Бенуа

0

Це впливає на анімацію рециркулятора, якщо це false.. анімація для вставки та видалення не відображатиметься. тож переконайтеся, що trueви додали анімацію для перегляду рециркулятора.


0

Якщо розмір RecyclerView (сам RecyclerView)

... не залежить від вмісту адаптера:

mRecyclerView.setHasFixedSize(true);

... залежить від вмісту адаптера:

mRecyclerView.setHasFixedSize(false);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.