Понимание RecyclerView setHasFixedSize

У меня проблемы с пониманием setHasFixedSize(). Я знаю, что он используется для оптимизации, когда размер RecyclerView не меняется, из документации.

Что это значит? В большинстве случаев ListView почти всегда имеет фиксированный размер. В каких случаях это не будет фиксированный размер? Означает ли это, что реальная площадь, которую он занимает на экране, увеличивается вместе с контентом?


person SIr Codealot    schedule 25.02.2015    source источник
comment
stackoverflow.com/a/40707099/1177959   -  person Sotti    schedule 20.11.2016
comment
Я нашел этот ответ полезным и очень простым для понимания [StackOverflow - rv.setHasFixedSize(true); ](stackoverflow.com /вопросы/28827597/)   -  person M. H.    schedule 14.01.2019


Ответы (8)


очень упрощенная версия RecyclerView имеет:

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

Эта ссылка объясняет, почему вызов requestLayout может быть дорогим. По сути, всякий раз, когда элементы вставляются, перемещаются или удаляются, размер (ширина и высота) RecyclerView может измениться, и, в свою очередь, может измениться размер любого другого представления в иерархии представлений. Это особенно проблематично, если элементы добавляются или удаляются часто.

Избегайте ненужных проходов макета, установив для параметра setHasFixedSize значение true, когда изменение содержимого адаптера не меняет его высоту или ширину.


Обновление. Документ JavaDoc был обновлен, чтобы лучше описать, что на самом деле делает метод.

RecyclerView can perform several optimizations if it can know in advance that RecyclerView's size is not affected by the adapter contents. RecyclerView can still change its size based on other factors (e.g. its parent's size) but this size calculation cannot depend on the size of its children or contents of its adapter (except the number of items in the adapter).

If your use of RecyclerView falls into this category, set this to {@code true}. It will allow RecyclerView to avoid invalidating the whole layout when its adapter contents change.

@param hasFixedSize — значение true, если изменения адаптера не могут повлиять на размер RecyclerView.

person LukaCiko    schedule 27.10.2015
comment
Размер RecyclerView меняется каждый раз, когда вы что-то добавляете, несмотря ни на что. Что делает setHasFixedSize, так это то, что он гарантирует (путем ввода пользователя), что это изменение размера RecyclerView является постоянным. Высота (или ширина) элемента не изменится. Каждый добавленный или удаленный элемент будет одинаковым. Если вы не установите это, он проверит, изменился ли размер элемента, и это дорого. Просто уточняю, потому что этот ответ сбивает с толку. - person Arnold Balliu; 25.05.2016
comment
@ArnoldB отличное разъяснение. Я бы даже сказал, что это отдельный ответ. - person young_souvlaki; 17.06.2016
comment
@ArnoldB - я все еще в замешательстве. Вы предлагаете установить для hasFixedSize значение true, если ширина/высота всех дочерних элементов постоянны? Если да, то что, если есть вероятность, что некоторые дочерние элементы могут быть удалены во время выполнения (у меня есть функция закрытия смахиванием по экрану) — можно ли установить значение true ? - person Jaguar; 30.06.2016
comment
Да. Потому что ширина и высота элемента не меняются. Он просто добавляется или удаляется. Добавление или удаление элементов не изменяет их размер. - person Arnold Balliu; 30.06.2016
comment
@ArnoldB Я не думаю, что размер (ширина / высота) предмета здесь является проблемой. Он также не будет проверять размер элемента. Он просто сообщает RecyclerView вызывать requestLayout или нет после обновления набора данных. - person Kimi Chiu; 02.01.2017
comment
Каким должно быть его значение по умолчанию (true/false), если мы не напишем эту строку? - person Meet Vora; 14.06.2017
comment
Размер @ArnoldBalliu RecyclerView меняется каждый раз, когда вы что-то добавляете, несмотря ни на что - это неправда. См. мой ответ: stackoverflow.com/a/53261923/5417224 - person bitvale; 12.11.2018
comment
@bitvale Существуют ли ваши RecyclerView во вселенной, где экраны телефонов адаптируются к размеру RecyclerView? Конечно, размер RecyclerView меняется с каждым визуализируемым элементом. С уважением, я не собираюсь смотреть на ваш ответ. - person Arnold Balliu; 13.11.2018
comment
@ArnoldBalliu Ты уверен? Размер RecyclerView меняется с каждым отображаемым элементом? Попробуйте узнать больше о жизненном цикле Views, в частности о жизненном цикле RecyclerView. Если RecyclerView имеет фиксированный размер или match_parent, а setHasFixedSize имеет значение true, его размер не изменился при рендеринге, но есть исключение, которого вы не видите в моем ответе. - person bitvale; 13.11.2018
comment
@bitvale Да. Динамические элементы могут быть рассчитаны только во время выполнения. Поэтому размер RecyclerView меняется. Поскольку setHasFixedSize существует, мы можем его отключить. Когда установлено, RecyclerView будет вычислять свою высоту перед рендерингом, и при рендеринге ему не нужно пересчитывать свой размер. Когда элемент добавляется, RecyclerView знает свой собственный размер и постоянно расширяется. Меняется ли его размер? Да. Является ли это изменение постоянным? Это зависит от того, установили ли вы setHasFixedSize. Я понятия не имею, что вы пытаетесь донести, что отличается от того, что все здесь уже сказали. - person Arnold Balliu; 14.11.2018
comment
@ArnoldBalliu Я считаю, что под размером мы подразумеваем разные вещи. Вы имеете в виду виртуальный размер, то есть размер RecyclerView, включая вещи, которые не отображаются на экране или не вписываются в родительский макет. Я (и документация) имею в виду реальный увеличенный размер. Ваши рассуждения о том, что размер детей одинаков, неверны. - person LukaCiko; 06.10.2020

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

Теперь вы можете использовать android:layout_height="wrap_content" в RecyclerView, что, среди прочего, позволяет CollapsingToolbarLayout знать, что он не должен сворачиваться, когда RecyclerView пуст. Это работает только тогда, когда вы используете setHasFixedSize(false) в RecylcerView.

Если вы используете setHasFixedSize(true) в RecyclerView, это поведение для предотвращения коллапса CollapsingToolbarLayout не работает, даже если RecyclerView действительно пуст.

Если бы setHasFixedSize было связано с размером элементов, это не должно иметь никакого эффекта, если в RecyclerView нет элементов.

person Kevin    schedule 28.09.2016
comment
У меня только что был опыт, который указывает на то же самое направление. Использование RecyclerView с GridLayoutManager (3 элемента в строке) и layout_height = wrap_content. Когда я нажимаю кнопку, которая добавляет 3 новых элемента в список, представление переработчика не расширяется, чтобы соответствовать новым элементам. Скорее, он сохраняет тот же размер, и единственный способ увидеть новые элементы — прокрутить его. Несмотря на то, что элементы имеют одинаковый размер, мне пришлось удалить setHasFixedSize(true), чтобы он расширялся при добавлении новых элементов. - person Mateus Gondim; 31.10.2016
comment
Я думаю ты прав. Из документа hasFixedSize: set to true if adapter changes cannot affect the size of the RecyclerView. Таким образом, даже если размер элемента изменится, вы все равно можете установить для него значение true. - person Kimi Chiu; 02.01.2017

Если у нас есть 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 для ширины или высоты в нашем RecyclerView

person Gastón Saillén    schedule 25.11.2019

У 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.
person dangVarmit    schedule 25.02.2015
comment
Но как определяется размер RecyclerView? Это размер, видимый только на экране, или полный размер RecyclerView, который равен (сумма высоты элемента + отступ + интервал)? - person Vicky Chijwani; 14.05.2015
comment
Действительно, для этого нужно больше информации. Если вы удаляете элементы, а recyclerview сжимается, считается ли, что размер изменился? - person Henrique de Sousa; 03.07.2015
comment
Я бы подумал об этом как о том, как TextView может выложить себя. Если вы укажете wrap_content, то при установке текста TextView может запросить проход макета и изменить объем занимаемого им места на экране. Если вы укажете match_parent или фиксированный размер, TextView не будет запрашивать проход макета, потому что размер фиксирован, а объем текста внутри никогда не изменит объем занимаемого пространства. RecyclerView такой же. setHasFixedSize() намекает RV, что ему никогда не нужно запрашивать проходы макета на основе изменений в элементах адаптера. - person dangVarmit; 06.07.2015
comment
@dangVarmit хорошее объяснение! - person howerknea; 27.06.2016

Когда мы устанавливаем 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 переработчика.

person bitvale    schedule 12.11.2018
comment
Вы тестировали RecyclerView высоты, используя wrap_content или match_parent? - person Lubos Mudrak; 14.12.2018

setHasFixedSize(true) означает, что у RecyclerView есть дочерние элементы (элементы) с фиксированной шириной и высотой. Это позволяет RecyclerView лучше оптимизировать, определяя точную высоту и ширину всего списка на основе вашего адаптера.

person Calvin Park    schedule 07.10.2015
comment
Это не то, что предложил @dangVarmit. - person strangetimes; 19.07.2016
comment
Вводит в заблуждение, на самом деле это размер представления Recycler, а не размер содержимого. - person Benoit; 16.04.2018

Это влияет на анимацию recyclerview, если это false.. анимация вставки и удаления не будет отображаться. поэтому убедитесь, что это true, если вы добавили анимацию для recyclerview.

person Alaa AbuZarifa    schedule 23.12.2017

Если размер RecyclerView (сам RecyclerView)

... не зависит от содержимого адаптера:

mRecyclerView.setHasFixedSize(true);

... зависит от содержимого адаптера:

mRecyclerView.setHasFixedSize(false);
person Alok Singh    schedule 06.08.2018