В каких случаях ViewHolder и View будут уничтожены а не переиспользованы

Ответ

ViewHolder и View не переиспользуются в следующих случаях:

  1. Изменение типа элемента – если getItemViewType() возвращает разные значения для позиций, RecyclerView создает новые ViewHolder'ы.
override fun getItemViewType(position: Int): Int {
    return if (isHeader(position)) HEADER_TYPE else ITEM_TYPE
}
  1. Нехватка пула RecycledViewPool – когда скролл создает больше ViewHolder'ов, чем вмещает пул.

  2. Изменение данных с notifyDataSetChanged() – RecyclerView не может сопоставить старые и новые позиции, поэтому пересоздает всё.

  3. Явное удаление – при вызове методов типа notifyItemRemoved() с последующим скроллом.

  4. Изменение layoutManager – при смене менеджера (например с Linear на Grid) все View пересоздаются.

Ответ 18+ 🔞

Слушай, вот реально интересный момент, который многих на проектах подставляет. Казалось бы, RecyclerView — умная хуйня, всё переиспользует, экономит память. Ан нет, есть подводные камни, где он эту свою оптимизацию нахуй отправляет и начинает всё с нуля лепить. Давай разберём, где он такую дичь выкидывает.

Первый случай — это когда у тебя элементы разного типа. Допустим, у тебя в ленте и посты, и рекламные баннеры, и разделители. Если в адаптере переопределён getItemViewType(), и для разных позиций он возвращает разные циферки, то RecyclerView не станет запихивать баннер в лайаут, предназначенный для поста. Это логично, но новички часто охуевают, когда видят, что при скролле создаются новые вьюхолдеры, хотя, казалось бы, старые должны переиспользоваться. Просто они другого типа, чувак!

override fun getItemViewType(position: Int): Int {
    return if (isHeader(position)) HEADER_TYPE else ITEM_TYPE // Вот тут и кроется причина
}

Второй момент — пул переиспользования просто забит. У RecyclerView есть этот самый пул (RecycledViewPool), где он складирует выкинутые за борт вьюхолдеры. Если ты листаешь так быстро, что создаёшь их овердохуища, пул переполняется, и новые экземпляры просто начинают создаваться, а старые — отправляются на свалку. Представь, будто ты в маленькую кладовку пытаешься запихнуть все чемоданы из отпуска — в какой-то момент дверь захлопнется, и новые чемоданы придётся ставить в коридор.

Третий пункт — самый пиздец и самая частая причина тормозов. Это когда ты обновляешь данные через notifyDataSetChanged(). Ты такой: «О, данные поменялись, дай-ка я всем об этом прокричу!». А RecyclerView получает этот сигнал и думает: «Бля, чёт нихуя не понятно, что изменилось. Ладно, проще всё выебать и пересоздать заново». И он так и делает — все вьюхи и вьюхолдеры летят в утиль, создаются новые. Доверия к этому методу — ебать ноль. Всегда старайся использовать точечные методы вроде notifyItemInserted() или notifyItemRangeChanged().

Четвёртое — это когда ты элементы явно удаляешь. Вызвал notifyItemRemoved(), элемент скрылся с анимацией, а потом проскроллил. Место, где он был, теперь вне зоны видимости, и RecyclerView может решить, что вьюхолдер оттуда уже можно прибить, особенно если он не поместился в тот самый переполненный пул.

Ну и пятый, довольно эпичный случай — смена layoutManager'а. Сидел у тебя список в линейном виде, а ты вдруг решил переключиться на сетку. RecyclerView смотрит на это и просто охуевает: «Мужик, это вообще другой способ компоновки, тут всё по-другому!». И он, не долго думая, накрывает весь текущий набор вьюхолдеров медным тазом и начинает жизнь с чистого листа. Все тщательно подготовленные и отбинденные вьюхи — коту под хвост.

Короче, суть в том, что умная либа — не волшебная. Если ты ей создаёшь условия, где она не может гарантировать корректность (разные типы вью) или ты сам её грубо обновляешь (notifyDataSetChanged), она выбирает путь наименьшего сопротивления — пизда рулю, пересоздаём всё. А ты потом сиди и гадай, отчего интерфейс лагает.