Понимание RecyclerView setHasFixedSize

136

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

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

Си Кодалот
источник
Я нашел этот ответ полезным и очень простым для понимания [StackOverflow - rv.setHasFixedSize (true); ] ( 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.

LukaCiko
источник
162
Размер RecyclerView меняется каждый раз, когда вы что-то добавляете, несмотря ни на что. Что делает setHasFixedSize, так это то, что он гарантирует (путем ввода пользователя), что это изменение размера RecyclerView является постоянным. Высота (или ширина) элемента не изменится. Каждый добавленный или удаленный элемент будет одинаковым. Если вы не установите этот параметр, он проверит, изменился ли размер элемента, и это дорого. Просто уточняю, потому что этот ответ сбивает с толку.
Арнольд Баллиу
9
@ArnoldB отличное уточнение. Я бы даже сказал, что это отдельный ответ.
young_souvlaki
4
@ArnoldB - я все еще в замешательстве. Вы предлагаете, чтобы мы установили hasFixedSize в true, если ширина / высота всех дочерних элементов постоянна? Если да, что, если есть вероятность того, что некоторые дети могут быть удалены во время выполнения (у меня есть возможность отклонить функцию) - можно ли установить значение true?
Ягуар
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 нет элементов.

Kevin
источник
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.
dangVarmit
источник
16
Но как определяется «размер» RecyclerView? Это размер, видимый только на экране, или полный размер RecyclerView, который равен (сумма высот элемента + отступы + интервал)?
Вики 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обновлению childs.

Но если мы установим 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метода повторного вызова отсутствуют .

bitvale
источник
Вы тестировали с 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 лучше оптимизировать, вычисляя точную высоту и ширину всего списка на основе вашего адаптера.

Calvin Park
источник
5
Это не то, что предложил @dangVarmit.
странное время
5
Вводит в заблуждение, это на самом деле размер представления Recycler, а не размер контента
Benoit
0

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

Алаа АбуЗарифа
источник
0

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

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

mRecyclerView.setHasFixedSize(true);

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

mRecyclerView.setHasFixedSize(false);
Алок Сингх
источник