Android studio adapter getitemviewtype

ziginsider

Заметка основана на статье Mateusz Dziubek “Multiple row types in RecyclerView”. Приводится пример создания RecyclerView c различными типами View. В процессе написания код подвергается рефакторингу. Показывается эффективность применения различных паттернов программирования и рефакторинга в целом на простом для понимания примере.

Постановка задачи

Итак, представим ситуацию, когда нам надо в один RecyclerView поместить несколько различных типов View. Допустим, это:

  • кнопка
  • картинка с текстом
  • заголовок с текстом

На смартфоне мы должны получить что-то вроде этого:

Вдохновляясь статьей Replace Conditional with Polymorphism, попробуем сделать это. Основная идея — каждому типу View соответсвует отдельный класс, и эти классы объединяются общим интерфейсом.

layout

Создаем, три layout, соответствующих трем типам View.

Например, для row_type_button.xml примерно так:
Для row_type_image.xm и row_type_text.xml аналогично. Или см. по ссылкам.

Классы для каждого типа View

Имея в виду row_type_button, создаем для него класс. Не забываем о конструкторе и обработчике нажатия:
Как видим, RowType и будет нашим объединяющим интерфейсом.

Далее, переходим к row_type_image. Незабываем о геттере для текста:
Наконец, row_type_text. Тут добавим геттеры для заголовка и текста:

Объединяющий интерфейс

В адаптере мы будем различать типы наших View посредством int getItemViewType(int position). Теперь вспомним об особенности интерфейсов, в силу уникальности, хранить в себе константы. Т.е. сигнатура int в интерфейсе соответсвует сигнатуре public static final int в классе наследующем интерфейс.

Учитывая данную особенность, сконструируем наш интерфейс для различения типов View:

Теперь в адаптере мы сможем различать типы View используя instanceof и, таким образом, возвращать тип View. Итак…

Адаптер

Получаем примерно такой адаптер:
Что сказать? Невооруженным взглядом можно видеть что кода много. Лично у меня возникает ощущение захламленности. Давайте улучшать ситуацию.

NB: Однако же все работает. Можно запустить и проверить. Реализацию MainActivity можно глянуть на github тут. Рефакторинг мы делаем исключительно из-за архитектурных соображений. В конечном итоге такой подход приносит пользу. Да и на код не страшно смотреть.

Рефакторинг

Во-первых, у нас три различных ViewHolder. Ничто не мешает нам применить к этой тройке шаблон проектирования “Фабрика”. Одной ответственностью станет меньше.

Заводим отдельный класс ViewHolderFactory и выносим туда наши холдеры. Внутри класса определяем функцию viewHolder create(parent, viewType), которая будет создавать необходимый холдер, согласно типу View:
Кроме того каждый data-класc, представляющий определенный тип View (ButtonRowType, ImageRowType, TextRowType), сам может возвращать тип своего View и самостоятельно Bind’ить ViewHolder. Для этого добавляем в интерфейс RowType две фунции:

Ниже пример реализации этих функций для класса TextRowType. Обратите внимание, что теперь нет необходимости в геттерах.

Полностью классы после рефакторинга см. github тут

Аналогично реализуем методы для остальных классов. В итоге получаем такой адаптер:
Кода в адаптере в три раза меньше. Код понятнее. Масштабировать/изменять проект гораздо легче.

Bonus

Допольнительно поговорим о более тонкой настройке RecyclerView.

RecyclerView с разными типами Item View, для того, чтобы избежать притормаживания при прокрутке, можно настроть чуть эффективнее.

Только сразу оговорим откуда может взяться притормаживание: в тех случаях когда в пуле RecyclerView (куда уходят ViewHolder после попадания за экран для последующего переиспользования) банально не будет места. Это может случится, например, при crossfade анимации всего списка элементов.

Дело в том, что по-умолчанию количество объектов конкретного View попадающих в пул RecyclerView равно 5. Причем все типы View у RecyclerView используют один пул объектов. Таким образом, зная что одни типы View появляются в нашем списке чаще, а другие реже, мы можем увеличить максимальное количество первых в пуле и уменьшить максимальное количество вторых. Как это сделать?

Читайте также:  Убрать кнопку домой с экрана андроид самсунг

Это важно знать. Т.к. допустим, элемент списка конкретного View, который как мы знаем на экране всегда представлен в одном экземпляре, может забивать пул объектов RecyclerView до пяти и, таким образом, четыре из этих ViewHolder будут напрасно хранится в пуле, занимая место, которое могли бы занимать элементы с типом View, которого на экране может быть больше.

Источник

Методы getViewTypeCount и getItemViewType для ArrayAdapter

Может ли кто-нибудь из простых слов объяснить мне использование методов getViewTypeCount() и getItemViewType() для ArrayAdapter ?

Они обрабатывают случай, когда вам нужны разные типы представлений для разных строк. Например, в приложении для контактов вам могут потребоваться, чтобы четные строки имели изображения с левой стороны и нечетные строки, чтобы иметь изображения справа. В этом случае вы будете использовать:

Структура использует ваш тип представления, чтобы определить, какие представления передать вам через convertView в вашем методе getView . Другими словами, в приведенном выше примере ваши четные строки будут получать только переработанные виды с изображениями с левой стороны для повторного использования, а нечетные строки получат только изображения с картинками справа.

Если каждая строка в вашем списке имеет одинаковый макет, вам не нужно беспокоиться о типах просмотров. Фактически, BaseAdapter.java обеспечивает поведение по умолчанию для всех адаптеров:

Это действительно дает вам одинаковый вид для каждой строки.

Изменить – чтобы определить общий поток:

  1. Вы привязываете данные к вашему AdapterView с помощью адаптера.
  2. AdapterView пытается отобразить элементы, которые видны пользователю.
  3. Структура вызывает getItemViewType для строки n , строки, которую она собирается отобразить.
  4. Фреймворк проверяет свой переработанный пул представлений для видов типа n . Это не находит, потому что пока никаких просмотров не было переработано.
  5. getView вызывается для строки n .
  6. Вы вызываете getItemViewType для строки n чтобы определить, какой вид просмотра вы должны использовать.
  7. Вы используете инструкцию if / switch для раздувания другого XML-файла в зависимости от того, какой тип вида требуется.
  8. Вы заполняете представление информацией.
  9. Вы возвращаете представление, выходя из getView , и представление вашей строки отображается пользователю.

Теперь, когда представление перерабатывается путем прокрутки экрана, оно переходит в переработанный пул представлений, который управляется инфраструктурой. Они по существу организованы по типу вида, так что представление правильного типа дается вам в параметре getView методе getView :

  1. Структура снова вызывает getItemViewType для строки, которую он хочет отобразить.
  2. На этот раз в переработанном пуле соответствующего типа есть вид.
  3. convertView представление передается вам как параметр convertView для вашего метода getView .
  4. Вы заполняете переработанное представление новой информацией и возвращаете ее.

Если нам нужно показать другой тип представления в представлении списка, тогда полезно использовать getViewTypeCount() и getItemViewType() в адаптере вместо переключения вида View.GONE и View.VISIBLE может быть очень дорогостоящей задачей внутри getView() которая будет Влияют на прокрутку списка.

Проверьте это для использования getViewTypeCount() и getItemViewType() в адаптере.

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

Вот мое объяснение:
И getViewTypeCount() и getItemViewType() используются методом BaseAdapter getView чтобы выяснить, какой тип представления должен быть извлечен, возвращен и возвращен. (Как объясняется в верхнем ответе в потоке). Но если вы не реализуете эти два метода интуитивно в соответствии с Android API Doc, тогда вы можете столкнуться с проблемой, о которой я упоминал.

Обобщенные руководящие принципы для реализации:
Чтобы реализовать несколько типов Views для строк ListView , мы должны по существу реализовать getItemViewType() и getViewTypeCount() . И документация getItemViewType() дает нам примечание следующим образом:

Примечание. Целые числа должны быть в диапазоне от 0 до getViewTypeCount() — 1 . IGNORE_ITEM_VIEW_TYPE также может быть возвращен.

Таким образом, в вашем getItemViewType() вы должны вернуть значения для типа просмотра, начиная с 0, до последнего типа (количество типов – 1). Например, допустим, у вас есть только три типа просмотров? Поэтому, в зависимости от объекта данных для представления, вы можете возвращать только 0 или 1 или 2 из getItemViewType() , например, индекс массива с нулевым значением. И поскольку у вас есть три типа просмотров, ваш getViewTypeCount() должен возвращать 3.

Читайте также:  Живые обои часы для андроид старинные

В любом случае, если вы вернете любые другие целочисленные значения, такие как 1, 2, 3 или 111, 222, 333 для этого метода, вы, безусловно, можете столкнуться с вышеуказанной ошибкой пользовательского интерфейса, которую вы только что разместили, не подчиняясь API-интерфейсу Android API.

Если вы не поняли ключ или не смогли решить проблему и нуждаетесь в дополнительной информации, прочитайте мой подробный ответ в этом потоке Q & A StackOverflow .

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

Надеюсь, этот ответ может быть полезен кому-то, чтобы спасти много часов!

Источник

Головная боль от RecyclerView.Adapter — выход есть

Привет, Хабр! Сегодня в нашем блоге Макс Туев, архитектор Surf, одной из наших сертифицированных студий. Ребята занимаются заказной разработкой, поэтому сроки важны не меньше, чем качество кода. Подходы и технологии, которые тормозят разработку, здесь не подходят. Хороший пример такого — RecyclerView.Adapter. Под катом Макс расскажет, как сэкономить время и нервы. Слово Максу.

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

Откуда растут ноги монстров? Основные проблемы создают 2 особенности адаптера:

  1. Управление позицией элементов (getItemViewType, onBindViewHolder, . )
  2. Определение, для каких элементов вызывать методы notifyItem.

Начнем со второго пункта

Эту проблему можно решить, используя notifyDataSetChanged(). Но тогда не получится использовать ItemAnimator, что было для нас недопустимо.

Решить это помог DiffUtil, который не так давно появился в Support Library. Этот класс позволяет определить, что изменилось в списке и оповестить об изменениях адаптер. Для этого нужно передать в него старый и новый списки. Вот хороший пример.

Проблема с notify, казалось бы, решена. Но все не так просто. К примеру, если мы изменим одно поле у объекта из списка, то DiffUtil не отработает. А такой код встречается очень часто. Если присмотреться, можно заметить, что для работы ему не нужен весь объект целиком. Нужен лишь id элемента для метода areItemsTheSame() и хеш от данных элемента для метода areContentTheSame() . Эту особенность и использовали для нашего решения.

Из каждого блока данных каждой ячейки экстрагируется id и hash при каждом их изменении. Затем полученный список из этих «высушенных» объектов сравнивается c помощью DiffUtil с таким же списком, собранным из прежних данных адаптера. Снаружи выглядит это примерно так:

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

Управление позицией элементов

Самое сложное в реализации и особенно поддержке адаптера с множеством ячеек — методы getItemCount() , getItemViewType() , onBindViewHolder() , onCreateViewHolder() . Они связаны между собой и обладают весьма специфической логикой. Вот, как может выглядеть один из них для списка с опциональным футером и хедером:

Читайте также:  Андроид камера снимает при выключенном экране

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

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

Мы взяли этот подход для своего решения, но использовали универсальные объекты-обертки. Так выглядит этот класс:

ItemController здесь отвечает, грубо говоря, за все взаимодействие с ячейкой. К нему мы еще вернемся.

В итоге все получилось немного сложнее, но суть осталась та же.

Ответственность за создание списка Item передается Activity или Fragment. Теперь нет необходимости расширять RecyclerView.Adapter, т.к. для реализации этого решения был создан универсальный EasyAdapter. Для наглядности сразу приведу пример метода Activity, обновляющего адаптер:

Списком в гифке выше, кстати, управляет именно этот метод.

Класс ItemList нужен, чтобы создавать списки было удобнее. Ряд его особенностей позволяет сильно упростить эту задачу и сделать код более декларативным:

  1. Цепочечный стиль заполнения.
  2. Нет явного создания объектов Item.
  3. Методы для добавления ячеек без данных.
  4. Методы для добавления ячеек с предикатом.
  5. Методы для добавления как единичных ячеек так и списка ячеек.

При вызове adapter.setItems() , будут, как говорилось ранее, вызваны необходимые методы notify.

В ItemList есть еще один важный метод — fun addAll(data: Collection, itemControllerProvider: ItemControllerProvider): ItemList. Он позволяет настраивать Adapter из списка объектов, расширяющих один базовый класс или интерфейс. Он пригодится, если логика конструирования списка нетривиальная и есть смысл перенести ее в Presenter.

Вернемся к ItemController и сразу посмотрим пример реализации:

Первое что бросается в глаза — полная инкапсуляция всех взаимодействий с ячейкой. Это позволяет как быстро и безопасно вносить изменения в список, так и полностью переиспользовать всю логику взаимодействия с ячейкой на других экранах. Еще ItemController отвечает за экстрагирование из данных id и hash, необходимых для правильной работы автоматического вызова методов notify.

Такая структура позволяет упростить еще кое-что:

  1. Не нужно реализовывать методы onBindViewHolder, getItemHash.
  2. Нет необходимости пробрасывать Listener внутрь Holder.
  3. ItemController для ячейки без данных будет еще проще.
  4. Не нужно придумывать названия для Holder и Listener(если еще пользуетесь java).
  5. Можно использовать шаблон ItemController для быстрой реализации.

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

Есть, правда, и несколько недостатков. Например, getChangePayload в DiffUtil.Callback игнорируется, и для каждой ячейки при изменении списка создаются объекты Item и ItemInfo (что, в принципе, можно решить при необходимости). Если собираетесь использовать гигантские списки — замерьте производительность (о производительности DiffUtil можно почитать здесь). Но чаще всего проблем с этим подходом у вас не возникнет.

Надеюсь моя заметка кому-то из вас пригодится и позволит тратить меньше времени и нервов на работу с RecyclerView.

Пример реализации с примерами использования здесь. Там же шаблон ItemController для AndroidStudio и базовый адаптер для пагинации на основе описанных подходов.

Большое спасибо разработчикам Surf, особенно Федору Атякшину (@rereverse), за помощь в разработке.

Источник

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