Android recyclerview get holder

Android RecyclerView with multiple view type (multiple view holder)

Jan 27, 2019 · 4 min read

We have explained earlier how to implement Android RecyclerView. But in that article, we have explained about a normal RecyclerView means each row has the same UI. That means a RecyclerView with only a single view type. Now in this article, we will implement RecyclerView with multiple view type.

Here it is an example of Android RecyclerView with multiple views. Let’s get started for implementation.

We have just change RecyclerView adapter to make it compatible with multiple views. For that, here we will make multiple view holder for the single RecyclerView.

Now here w e will create a RecyclerView with two different views. One for call layout and another one for email layout. So let’s create two layouts for two different items in recyclerview: item_call.xml and item_email.xml

Now we have one model class Employee. We use the same model class for both view holders. In that class, we have two fields, phone and email. So if we keep phone blank that means value for email is mandatory and same vice-versa if we keep email blank that means a value of a phone is mandatory. So if a phone is blank then RecyclerView shows email UI and if an email is blank then RecyclerView shows call UI. Let’s create ArrayList for that.

Now let’s do code in the recyclerview adapter.

Normally we extend adapter class to RecyclerView.Adapter but here there are multiple view holders. So we extend adapter to RecyclerView.Adapter . So 3 overrided methods are there. onCreateViewHolder, onBindViewHolder and getItemCount in recyclerview adapter class.

Now override another method getItemViewType() and code as below in that method. This is our viewType logic that how we differentiate employees ArrayList from call to email.

TYPE_CALL and TYPE_EMAIL are two static values with 1 and 2 respectively in adapter class.

Now create view holder with multiple views like this:

Now code as below in onCreateViewHolder and onBindViewHolder method in recyclerview adapter:

Create method setCallDetails and setEmailDetails in CallViewHolder and EmailViewHolder respectively.

That’s it. Android RecyclerView with multiple views (multiple view holder) implementation complete. You can create more complex RecyclerView using this like Flipkart dashboard and facebook feed page. Yes, you can create more and more multiple views in a single RecyclerView.

You can also add header in RecyclerView and footer in RecyclerView using this feature. Here in below example, there are only 3 types: header, footer and call in a single recyclerview using multiple recyclerview view types. Try your self for this kind of UI.

Источник

О RecyclerView и выделении элементов

Содержание

1. Немного о ViewHolder’ах

До выхода в свет Android SDK 5.0 Lollipop для отображения списков и таблиц использовались виджеты ListView и GridView. Общей рекомендацией при работе с этим виджетом было использование паттерна ViewHolder. Суть паттерна заключается в том, что для каждого элемента списка создаётся объект, хранящий ссылки на отдельные вьюхи внутри элемента. Таким образом, приходится выполнять достаточно дорогой вызов findViewById(int) только один раз при создании элемента.

Пример типичного ViewHolder’а прямиком из руководств гугла:
Cсылка на такой холдер для каждого элемента сохраняется в корневом layout’е, используя метод setTag(int, Object) (с моей точки зрения тот ещё костыль).

2. Вкратце о RecyclerView

К выходу Android SDK 5.0 Lollipop разработчиков Google наконец-то озарило, что два вышеперечисленных виджета морально устарели и нужно бы заменить их на нечто более стильное, модное и молодёжное. Было принято решение не переделывать старые виджеты, а написать новый. Так и появился на свет RecyclerView. Так в чём же его отличия, спросите вы?

Я приведу вкратце основные, а для более полного их раскрытия советую к ознакомлению вот эту статью на хабре. Итак:

  1. Сам виджет больше не берёт на себя обязанность по размещению элементов. Для этого появились LayoutManager’ы.
  2. Паттерн ViewHolder стал обязательным. Причём виджет научился заново использовать уже созданные ViewHolder’ы и удалять уже не используемые (отсюда и название), что благоприятно сказывается на быстродействии и размере используемой памяти.
  3. Новый, удобный способ работы с анимацией.
Читайте также:  Плеер для андроид с ускорением воспроизведения

Я попробовал его и виджет оставил у меня противоречивые впечатления. С одной стороны, да, здорово, что теперь использование ViewHolder’а является обязательным, работает вроде тоже быстрей, памяти жрёт меньше. С другой стороны, есть проблемы со сложностью и недоделанностью виджета.

Что я понимаю под сложностью? Если что-то не работало в ListView (или работало не так как задумано) всегда можно было залезть в исходники, разобраться, в чём ошибка, исправить её, подоткнуть костылей тут и там и всё начинало работать. RecyclerView гораздо сложнее в плане логики работы, мозг сломаешь, пока разберёшься. Я пытался, но забросил, уж слишком много времени и сил для этого нужно.

Вторая проблема — банальное отсутствие функционала, присутствовавшего в ListView и GridView. За примерами далеко ходить не надо — стандартный функционал выделения элементов (дальнейшая тема этой статьи), отступы между элементами. Раньше, чтобы добавить всё это, нужно было написать буквально пару строчек кода, теперь на это уйдут уже десятки строк. Есть анимации, но только для добавления/удаления/редактирования элемента. Если же вы хотите, например, анимировать частичное изменение элемента, то к вам в дверь уже стучится птица обломинго. Виджет не поддерживает анимацию части элемента, и если анимировать элемент извне (из адаптера, например), то лучше этого не делать — подобные манипуляции оставляют элементы виджета (те самые ViewHolder’ы) в неопределённом состоянии, что приводит к совершенно фантастическому поведению вашего списка.

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

3. Выделяем элементы

Итак, перейдём к главному — к технической части статьи. Поговорим о том, как выделять элементы в RecyclerView. Сразу оговорюсь — идея реализации почерпнута из замечательной серии статей Билла Филлипса про RecyclerView (ссылки в конце), так что всё нижеследующее можно считать вольным кратким пересказом.
В ListView для выделения элементов использовался метод setChoiceMode(int), RecyclerView же понятия не имеет, что элементы могут выделяться, поэтому мы должны научить этому наш адаптер.

Схема такая:
На диаграмме я схематично обозначил связи между объектами. Пунктирные стрелки — ссылки, остальные — вызовы методов. Зелёным я обозначил объекты, которые непосредственно реализуют логику выделения.

Принцип работы получается следующий:

  1. ViewHolderWrapper устанавливает себя в качестве ClickListener’а для корневой вьюхи ViewHolder’а и начинает получать события onClick и onLongClick. В зависимости от реализации он может просто проксировать эти события в HolderClickObservable (ViewHolderClickWrapper), либо, исходя из текущего статуса SelectionHelper’а выделять элемент вызовом setItemSelected(ViewHolder, boolean) (ViewHolderMultiSelectionWrapper).
  2. SelectionHelper сохраняет информацию о выделенных элементах и оповещает слушателей (SelectionObserver) об изменении выделения.
  3. Слушатель (в нашем случае адаптер) отвечает за визуальное отображение выделения элемента, а также взаимодействия с ним (на диаграмме вызов startActionMode у Activity).

В самом адаптере необходимо сделать следующие изменения:

1. Создать SelectionHelper и зарегистрировать слушателей (в данном случае сам адаптер, но это может быть и Activity, например)
2. Обернуть создаваемые ViewHolder’ы во ViewHolderWrapper нужного типа.Метод wrapSelectable(ViewHolder) у SelectionHelper’а:
3. Не забывать прицеплять наши ViewHolder’ы к SelectionHelper’у в методе onBindViewHolder(ViewHolder, int) нашего адаптера!
Это нужно по причине того, что пока нет другого способа получить от RecyclerView список используемых в настоящий момент ViewHolder’ов. Если не вести их учёт, при необходимости обновить отрисовку выделения у всех выбранных элементов (пользователь закрыл ActionMode, например), SelectionHelper просто не сможет этого сделать. Вьюхи останутся выглядеть выделенными, когда по факту таковыми не будут.

Вы спросите — «А почему бы просто не запоминать выделяемые ViewHolder’ы в методе setItemSelected(ViewHolder, boolean)?». Тут как раз сказывается особенность RecyclerView — он использует заново уже созданные ViewHolder’ы.

Выглядит это примерно так:

  1. Открываем приложение. На экране 10 элементов — 10 ViewHolder’ов создано для них.
  2. Запускаем ActionMode, начинаем выделять элементы — 1,2,3.
  3. Прокручиваем вьюху вниз, видим элементы с 10 по 20. Думаете, что теперь в памяти висит 20 ViewHolder’ов? Как бы ни так! Для части данных RecyclerView создаст новые ViewHolder’ы, а для другой заново использует уже имеющиеся. Причём неизвестно в каком порядке.
  4. Теперь если мы прокрутим вьюху обратно вверх, часть из наших 10 ViewHolder’ов будет уничтожена, вместо них будут созданы новые. Оставшаяся часть будет использована заново и совершенно не обязательно для тех же позиций.
  5. Отменяем ActionMode. SelectionHelper должен раскидать слушателям уведомления о сменившемся выделении на элементах с указанием ViewHolder’а для каждого элемента, но он уже не владеет актуальными данными, все Holder’ы поменялись!
Читайте также:  Виджет батарея для экрана андроид

В результате это приведёт к тому, что часть элементов останется отображаться с выделением.

И здесь становится очевидным ещё один важный момент — нельзя сохранять строгие ссылки (strong reference) на ViewHolder’ы! Они могут быть удалены из RecyclerView в зависимости от фазы Луны и желания левой пятки Ларри Пейджа. В этом случае, если мы будем хранить строгие ссылки на них, случится утечка памяти. Поэтому для хранения ссылок в ViewHolderWrapper и WeakHolderTracker используются только WeakReference.

4. Также важно не забыть в onBindViewHolder(ViewHolder, int) визуально отобразить выделение если оно есть (если нет — не забыть убрать!). Вы же помните, что для не выделенного элемента может быть использован ViewHolder, ранее использовавшийся для не выделенного и наоборот?
У меня это реализовано следующим образом:

4.1. SelectableRecyclerViewAdapter.onBindViewHolder(ViewHolder, int)
4.2. layout-файл для элемента

CheckableAutofitHeightFrameLayout добавляет к FrameLayout всего 2 вещи: во-первых, он всегда квадратный (смотри onMeasure(int, int)) и, во-вторых, добавляет к DrawableStates (те самые, которые используются в xml) состояние state_checked. В результате, для отображения выделения у такого layout’а можно использовать StateListDrawable на вроде этого:и все детали отображения уползают в xml-ки, в Java только нужно установить соответствующие состояния.

5. Передать событие onSelectableChanged(boolean) в Activity и запустить ActionMode:

Как вы видите, при запуске ActionMode, она регистрирует себя как SelectionObserver. Таким образом, можно обновлять количество выделенных элементов в заголовке. Не забудьте вызвать unregisterSelectionObserver(SelectionObserver) при закрытии!

4. Заключение + Бонус

Кажется, с выделением разобрались. Весь исходный код также можно посмотреть на GitHub.

В заключение вкратце приведу ещё несколько фишек для работы с RecyclerView, которые вы можете найти в примере.

1. Если не нужно выделять элементы, а нужно просто обрабатывать нажатия, вместо ViewHolderMultiSelectionWrapper оборачивайте элементы в ViewHolderClickWrapper методом wrapClickable(ViewHolder). Сам адаптер в таком случае будет выглядеть примерно так:

Виджет подбирает ширину столбцов в зависимости от параметра columnWidth. Важный момент: если доступная ширина 330 пикселей, а мы передадим желаемую ширину 100, в итоге в таблице будет 3 столбца по 110 пикселей и элементы будут этой ширины. Именно поэтому я также сделал CheckableAutofitHeightFrameLayout автоматически изменяющим свою высоту в зависимости от ширины.

3. Для добавления отступов между элементами можно выставить paddingTop/Left у RecyclerView и marginRight/Bottom у элементов, однако это выглядит как костыль. Рекомендуемым способом является добавление ItemDecoration к RecyclerView. В примере можно найти несколько. Для добавления отступов к обычному GridLayoutManager (под «обычным» я имею ввиду GridLayoutManager со стандартным SpanSizeLookup, в нём каждый элемент занимает 1 span) можно использовать

Источник

Фантастические RecyclerView.ViewHolder и где они создаются

Давайте представим, что вы уже cоптимизировали ваш ресайклер вдоль и поперек:

вьюшки более плоские чем Земля

самый быстрый биндинг вьюхолдеров на Диком Западе

Но вам этого мало и вы продолжаете искать пути оптимизации. Поздравляю, вы попали в правильную статью!

Предыстория

Как-то раз, крася и двигая кнопки, я заметил, что ресайклер вью на одном из наших экранов немного проседал по отрисовке при активном скролле из-за большого разнообразия viewType (и, как следствие, частого создания новых вьюхолдеров), и, глядя на это, вспомнил о старой статье Chet Haase про то, как появилась возможность нагружать ресайклер работой по префетчингу элементов в моменты простоя мэйн трэда. Но мне показалось этого мало, и захотелось создавать элементы в моменты простоя. не мэйн трэда.

Так появилась идея подтюнить работу RecyclerView.RecycledViewPool для того, чтобы он заполнял себя вьюшками созданными off the main thread и отдавал их ресайклеру по запросу, т.е. занимался префетчингом.

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

Эту идею я решил разбить на две составляющие: одна будет заниматься созданием вьюшек в бэкграунде (Supplier), а другая — их получением и последующей передачей ресайклеру в пользование (Consumer).

RecyclerView.RecycledViewPool

Для начала стоит разобратся с тем, что такое RecyclerView.RecycledViewPool , чтобы на его основе собрать наш Consumer.

RecycledViewPool — это хранилище переработанных вьюшек, из которого мы можем их доставать для нужного нам viewType , тем самым не создавая их лишний раз. Таким образом, именно RecycledViewPool лежит в основе богоподобной производительности RecyclerView . Помимо просто хранения переработанных вьюшек RecycledViewPool также хранит информацию о том, сколько примерно по времени вьюшки создаются и биндятся — с тем, чтобы в дальнейшем, по запросу GapWorker , достаточно правдоподобно «предсказывать», удастся ли эффективно утилизировать оставшееся время в кадре для того, чтобы упредительно создать вью того или иного viewType .

Читайте также:  Включить smart lock android 11

Consumer

Теперь, когда мы разобрались с тем, что за зверь такой RecyclerView.RecycledViewPool — можно приступить к модификации его функциональности в угоду нашим потребностям (заблаговременное наполнение не только по запросу от GapWorker , но и от нашего Supplier)

Прежде всего, мы хотим добавить в API нашего префетчера возможность конфигурировать количество вьюшек того или иного типа, которые следует. префетчить.

Здесь, помимо сохранения самого значения максимального числа хранимых вьюшек мы также сообщаем ViewHolderSupplier данные о том, сколько мы хотим от него вьюшек определенного типа посредством вызова setPrefetchBound , тем самым запуская процесс префетчинга.

Далее, при попытке переработать вьюшку, мы будем сообщать вью пулу количество максимально возможных хранимых вьюшек, чтобы не хранить лишнего и актуализировать знание об этом числе, поскольку оно может меняться с течением времени.

Затем, когда к нашему вью пулу будет приходить запрос от ресайклера на получение ранее переработанной вьюшки, в случае если, в нашем пуле не окажется подготовленного/переработанного экземпляра, мы уведомим наш Supplier о том, что ресайклер сам создаст очередную вьюшку этого типа на мэйн трэде.

Осталось только связать жизненный цикл нашего вью пула и Supplier, и определить, что наш вью пул является Consumer‘ом префетчнутых вьюшек:

Здесь стоит отметить метод factorInCreateTime — это метод, который сохраняет время создания вьюхолдера, на основе которого GapWorker будет делать выводы о том, сможет он что-нибудь префетчнуть своими силами в моменты простоя мэйн трэда или нет.

Supplier

Теперь, когда мы разобрались с первой частью нашего механизма, давайте разберемся с реализацией второй — Supplier. Его основная задача — запускать очередь на создание вьюшек требуемого viewType , создавать их где-то вне мэйн трэда и передавать Consumer. Помимо этого, чтобы не делать лишней работы, ему следует реагировать на то, что вьюшка была создана за его пределами, уменьшая размер той самой очереди на создание.

Запускаем очередь

Для запуска очереди на создание проверяем нет ли у нас в очереди уже достаточного количества вьюшек этого типа. Дальше проверим, не насоздавали ли мы уже достаточно вьюшек этого типа. И если оба эти условия проходят — добавляем в очередь запрос на то количество вьюшек, которое достаточно для того, чтобы довести количество уже созданных вьюшек до целевого значения count :

Создаем вьюшки

Что касается метода enqueueItemCreation — он будет абстрактным и его реализация будет зависеть от выбранного вашей командой подхода к многопоточности.

Но вот, что точно не будет абстрактным, так это метод createItem , который предполагается вызывать откуда-то из-за пределов мэйн трэда как раз посредством реализации enqueueItemCreation . В этом методе, прежде всего мы проверяем, а, вообще, нужно ли что-то делать или мы уже располагаем достаточным количеством закэшированных вьюшек? Затем мы создаем нашу вьюшку, запоминая время, затраченное на ее создание, игнорируя ошибки (пусть они там у себя в мэйн треде падают). После этого мы сообщим новому вьюхолдеру его viewType , просто чтобы он в курсе был, сделаем пометку, что создали еще один элемент с нужным viewType и оповестим Consumer‘а об этом же.:

Здесь viewHolderProducer это простой typealias :

(на роль которого подходит RecyclerView.Adapter.onCreateViewHolder , например)

Стоит упомянуть, что традиционное создание вью через LayoutInflater.inflate — не самый эффективный способ утилизировать возможности представленного в статье механизма в силу синхронизации на аргументах конструктора.

Реагируем на создание вьюшки вне Supplier

В случае создания вьюшки за пределами нашего Supplier (метод onItemCreatedOutside ) — просто обновляем текущее знание о количестве уже созданных вьюшек данного типа:

Profit!

Теперь достаточно только определить поведение методов start , stop и enqueueItemCreation в зависимости от выбранного вашей командой подхода к работе с многопоточностью и вы получите вундервафлю по созданию вьюхолдеров за пределами основного потока, которую можно даже переиспользовать между экранами и подсовывать различным ресайклерам использующим одинаковые адаптеры/вьюшки, например.

Ну, а в случае, если вам не хочется задумываться над тем, как же все таки написать свою реализацию ViewHolderSupplier , то, только сегодня, специально для вас, я сделал небольшую библиотечку, в которой есть артефакт с core функциональностью, описанной в этой статье, а также артефакты под все основные подходы к многопоточности современности (Kotlin Coroutines, RxJava2, RxJava3, Executor).

Источник

Оцените статью