Android recyclerview wrap content

Простой вариант разношерстного recycler view на шаблоне Посетитель

Прошло полгода, как я с паскаля перекатился на kotlin и влюбился в android-разработку, и вот уже разрешаю себе публично лезть со своими идеями в чужой монастырь. Но причина на то есть. Понаблюдав в профильных чатах за тем, какие чаще всего возникают вопросы у android-разработчиков, и не только у новичков, я понял, что в большинстве случаев, когда человек сталкивается с ошибкой, которую не может понять, как не может понять объяснение коллег из чата или их наводящие вопросы, причиной является бездумное использование готовых кусков кода или библиотек. Однако, полагаясь на готовые примеры кода, которые у них не работают (а в этой сфере код, написанный больше года назад, по умолчанию требует обновления или вообще переработки, и это касается кода со stack overflow, библиотечных гайдов и даже гайдов от самого Google), они не понимают причин возникающих ошибок или же отличающегося поведения, поскольку полагаются на библиотеку как китайскую комнату, не пытаясь разобраться в её архитектуре и принципах работы.

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

Изучая архитектурные шаблоны android-разработки, я приучил себя в первую очередь искать ответы на сервере Google developer guides. Но иногда там, особенно в обучающих codelabs, приводятся примеры кода больше упрощенного, чем рассчитанного на универсальность, чистоту и расширяемость.

В данном случае у меня возникла потребность использовать модный recycler view для отображения списка элементов с разной внутренней разметкой и логикой. На такой идее строятся все современные приложения — от мессенджеров и лент социальных сетей до банковских приложений. К тому же комбинирование на лету с использованием реактивного подхода разных визуальных элементов списка recycler view вместо ручной верстки разметки является мостиком в мир декларативно-функционального ui, который нам предлагают в Jetpack Compose, и на который рано или поздно Google мягко предложит переходить.

Codelab, посвященный включению в список recycler view элемента с другой разметкой, строится на оборачивании элемента списка внутрь sealed класса. Но это не главный недостаток. Главный, на мой взгляд,- помещение всего кода, обрабатывающего разные элементы списка, внутрь класса самого адаптера. Это заставит в будущем расти код адаптера как снежный ком, нарушая как принцип открытости/закрытости, так и принцип единственной ответственности (при желании, можно найти нарушения каждой буквы из акронима SOLID, но поэтому их и объединили).

Другим существенным минусом является то, что Google предложил вынести свойство id из data-классов в качестве дискриминатора двух типов элементов: для заголовка id будет равен Long.MIN_VALUE, а для данных id будет транзитом переходить из data-класса. И здесь полностью закрыта щелочка для дальнейшего расширения: у вас или data-класс, который для адаптера будет всегда одинаков, или заголовок. Вся архитектура могучего recycler view мгновенно сжалась до всего лишь двух вариантов.

Решением проблемы можно считать использование готовых библиотек. Я из самых актуальных и наиболее распространенных нашел adapter delegates, groupie и epoxy. По ним по всем написаны многие статьи как здесь, так и там. Самый базовый подход, который я собираюсь сейчас изложить, наиболее близко воплощен в первой библиотеке. Группи и эпокси гораздо мощнее, универсальнее, но при этом сложнее внутри и станут сложнее снаружи, если вдруг разработчику захочется использовать всю их мощь.

Любая библиотека всегда таит в себе две беды:

вам бывает лень разбираться в ее устройстве, в итоге вы не осознаете, что используете библиотеку всего лишь на 10%, прямо как мозг у некоторых млекопитающих;

вы зависите от библиотечных классов: вам или надо наследовать ваши данные от них, или еще как-то ломать свои представления об идеальных data-классах и их движении.

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

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

Так вот, в самом широком смысле в этих библиотеках применяется паттерн Посетитель с менеджером.

Адаптер recycler view, то есть обычный ListAdapter, уже имеет все необходимые методы, которые подталкивают к использованию Посетителя:

getItemType — функция, которая должна возвращать тип элемента (поскольку тип в данном случае всего лишь целое число, Google рекомендует использовать более понятные константы);

onCreateViewHolder — функция, которая возвращает ViewHolder такого класса, который реализован для требуемого типа элемента (тип передается в функцию параметром с помощью предыдущей функции);

onBindViewHolder — функция, которая осуществляет привязку конкретного элемента в списке (передается по номеру) и конкретного ViewHolder, который создан и возвращен предыдущей функцией.

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

Если вы реализуете стандартный шаблон DiffCallback,

то всегда помните, что areContentsTheSame вызывается только тогда, когда areItemsTheSame возвращает true. В моем примере классы реализуют интерфейс HasStringId, в котором есть id типа String и метод equals, что позволяет использовать data-классы как для моделей данных, так и для моделей слоя view. Data-классы с данными из сети всегда имеют уникальный id, поэтому их отличие DiffUtil определяет максимально быстро, а для вспомогательных ui-классов с одинаковыми id вызываются оба метода.

Чтобы управлять набором посетителей, введем такое понятие, как менеджер. Это будет класс, реализующий следующий интерфейс:

И определим для целей тестового примера набор типов для recycler view:

Собственно менеджер и будет тем «делегатом» в терминологии adapter delegates, которому адаптер делегирует функции по определению типа для отрисовки текущего элемента. Для этого в менеджере должны быть зарегистрированы все необходимые классы вью холдеров.

Читайте также:  Самый хороший смартфон андроид

Я буду использовать hilt и data binding, поскольку с их помощью очень легко решить второстепенные задачи: внедрить менеджера вью холдеров и упростить отображение данных на ui. Бонусом будет точное знание того места, где именно и в какой момент инициализируется менеджер вью холдеров, а также какие вью холдеры регистрируются в нем:

Добавим верстку для всех элементов:

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

Здесь два стандартных для Посетителя метода (классически они называются acceptVisitor и execute, однако мы же пишем не абстрактного посетителя для реализации паттерна в вакууме, а весьма конкретное его приложение) — acceptBinding и bind, а также свойство layout, в которое конкретные вью холдеры будут записывать ссылку на ресурс своей разметки.

Роль функции accept заключается в следующем: когда адаптер просит (или требует) от менеджера выдать ему соответствие типа элемента тому объекту, который должен быть отрисован, менеджер пробегается по всем зарегистрировавшимся вью холдерам, вызывая их метод accept, и выдает тип первого, который вернет true. Таким образом, ни адаптер, ни менеджер не знают ничего о внутреннем устройстве и даже классе самих элементов, что позволяет бесконечно увеличивать их количество и изменять как угодно. Единственное требование — зарегистрировавшийся вью холдер должен честно признаваться (accept = true), что готов забиндить какой-то элемент, тип которого он указал при регистрации.

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

И для примера вью холдер карточки (другие реализации очевидны и практически аналогичны):

Не стоит бояться операторов явного приведения типов as в коде. Во-первых, реализуя интерфейс, вы заключаете определенный контракт: если функция accept согласна с тем, что Посетитель работает с элементами класса CardItem, в метод bind совершенно точно будет передан объект только этого класса и никакого другого. Это же касается и разметки: если вы однозначно определяете имя ресурса разметки в свойстве layout, именно для этой разметки в свойство binding будет передаваться сгенерированный data binding класс. Ну и во-вторых, если бы это не было безопасно, разве линтер idea или android studio не разразились ли громкими воплями на всю сборку?

Что касается самого главного, адаптера для recycler view,- он единственный и универсальный, поскольку все функции по различению разных элементов делегирует менеджеру, а тот в свою очередь выбирает одного единственного холдера, согласного посетить и прибиндить намертво элемент к экрану смартфона:

Сам адаптер создается и наполняется элементами уже в слое view, в данном случае во фрагменте таким нехитрым способом:

Благодаря одному «лишнему» классу менеджера вью холдеров и паре интерфейсов мы получили простой, но очень функциональный recycler view с возможностью безболезненного расширения. К тому же в противовес библиотекам у нас остались следующие преимущества:

нет необходимости наследовать классы данных от библиотечных супер-классов;

нет необходимости оборачивать разные элементы в sealed класс;

один и тот же data-класс можно использовать для сериализации/десериализации данных из интернета, сохранения в локальной базе и в качестве модели для view или data биндинга;

изменения классов данных и вью-холдеров не влекут изменений кода адаптера;

все внутренности элементов инкапсулированы от адаптера, законы SOLID выполняются;

отсутствует избыточная функциональность, неизбежно приносимая библиотеками (YAGNI).

Разумеется, моя реализация еще имеет пути для улучшения и расширения. Можно, как в groupie добавить группировку элементов и их визуальное сворачивание. Можно отказаться от data binding или дополнить адаптер вариантами для view binding или обычного инфлейта разметки со всеми любимыми findViewById во вью холдерах. И тогда код превратится в ту же самую библиотеку, которых уже вон сколько и так. Для моих же конкретных целей на тот момент, когда возникла необходимость, варианта с простым Посетителем более, чем достаточно:

Прошу не судить строго, поскольку в мире android это мое первое появление на свет. Полноценный код примера из текста статьи будет доступен в репозитории github.

Источник

RecyclerView

RecyclerView — компонент для отображения элементов списка, который является более продвинутой и гибкой версией ListView , но не является его родственником, а относится к семейству ViewGroup .

Принцип работы

Для отображения данных RecyclerView использует несколько компонентов:

  • Объект RecyclerView , который нужно добавить в макет. Он заполняется элементами списка в зависимости от того, какой был установлен LayoutManager . Существуют стандартные LayoutManager ‘ы, например, LinearLayoutManager отображает элементы в виде списка, а GridLayoutManager — в виде сетки. Но можно создать и свой собственный LayoutManager .
  • Элементы списка представлены в виде объектов viewHolder . Например, если список состоит из различных видов деревьев, то viewHolder — это конкретный вид дерева — сосна, яблоня, берёза и т.д. RecyclerView создает столько объектов viewHolder , сколько требуется для отображения на экране устройства и несколько про запас. Когда пользователь начинает прокручивать список, RecyclerView берёт те объекты viewHolder , которые ушли за пределы экрана и “привязывает” к ним новые данные.
  • Объекты viewHolder управляются адаптером. Он создаёт объекты viewHolder и привязывает к ним информацию.

Добавление RecyclerView в проект

Библиотека

По умолчанию (при создании нового проекта) функциональность RecyclerView не доступна. Поэтому для начала нужно добавить соответствующую библиотеку. Для этого в файл build.gradle , который находится в папке app , добавьте одну из библиотек:

Версия support library уже вряд ли когда-нибудь изменится, так как её поддержку остановили. А вот за версией androidx нужно следить.

После того, как библиотека добавлена, обязательно нажмите на кнопку Sync Now, чтобы изменения вступили в силу.

Добавление в макет

Библиотека добавлена, а значит теперь мы можем обращаться к RecyclerView . Первым делом следует добавить его в макет. Он может быть добавлен как дочерний элемент другого компонента:

Либо может быть единственным (корневым) компонентом макета:

Далее для корректной работы RecyclerView требуется установить LayoutManager и адаптер. LayoutManager может быть установлен двумя способами. В макете:

Либо вместе с адаптером в классе фрагмента или активити:

При этом начиная с версии Android Studio 3.6 необязательно вызывать метод findViewById(), а можно напрямую обратится к компоненту из макета по его идентификатору (как в примере выше).

Макет элемента списка

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

А можно воспользоваться стандартными макетами, к которым можно обращаться через android.R.layout.НАЗВАНИЕ_ИЗ_СПИСКА . Но на мой взгляд это вариант для ленивых или для любопытных.

Адаптер и viewHolder

Адаптер — это класс, который занимается передачей данных в список, созданием объектов viewHolder и их обновлением. Адаптер должен наследоваться от класса RecyclerView.Adapter .

ViewHolder — это тоже класс, объекты которого адаптер использует для хранения и визуализации элементов списка. ViewHolder должен наследоваться от класса RecyclerView.ViewHolder . Как правило этот класс располагают внутри адаптера.

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

В классе адаптера нужно обязательно переопределить 3 метода:

  • onCreateViewHolder() — данный метод вызывается LayoutManager ‘ом, чтобы создать объекты viewHolder и передать им макет, по которому будут отображаться элементы списка.
  • onBindViewHolder() — данный метод вызывается LayoutManager ‘ом, чтобы привязать к объекту viewHolder данные, которые он должен отображать.
  • getItemCount() — возвращает общее количество элементов в списке.

А в классе ViewHolder требуется указать используемые компоненты разметки.

Последние штрихи

Теперь адаптер настроен и готов к использованию. Осталось только его подключить. Делается это в классе фрагмента или активити (в моём примере используется фрагмент).

Если какой-либо элемент списка изменился, то следует вызвать метод адаптера notifyItemChanged() и передать ему позицию элемента, которую требуется обновить. Вместо него можно использовать метод notifyDataSetChanged() , который будет обновлять полностью весь список, но из-за этого он является ресурсозатратным.

Создание RecyclerView с помощью шаблона

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

Кликните по любому файлу правой кнопкой мыши и в появившемся контекстном меню выберите: New > Fragment > Fragment (List).

Стандартные LayoutManager’ы

RecyclerView использует LayoutManager для того, чтобы расположить элементы списка на экране. При этом каждый LayoutManager позволяет расположить их по-своему.

Существует три стандартных LayoutManager ‘а:

  • LinearLayoutManager — упорядочивает элементы в виде обычного вертикального или горизонтального списка.
  • GridLayoutManager — размещает элементы в виде сетки одинакового размера.
  • StaggeredGridLayoutManager — размещает элементы в виде неравномерной сетки: каждый столбец будет слегка смещён по сравнению с предыдущим.

Как правило этих вариантов достаточно для большинства ситуаций. Но если это не ваш случай, то можно создать свой собственный LayoutManager , расширив класс RecyclerView.LayoutManager .

LinearLayoutManager

По умолчанию LinearLayoutManager упорядочивает элементы в виде вертикального списка.

У данного класса есть другой конструктор, который позволяет явно задать ориентацию списка. Помимо контекста, ему требуется два параметра:

  • Ориентация — задаётся с помощью констант HORIZONTAL и VERTICAL класса LinearLayoutManager .
  • Булево значение: если передать true — список будет установлен в обратном порядке (начало будет в конце).

Эти параметры можно устанавливать с помощью специальных методов:

Либо с помощью специальных атрибутов в XML:

GridLayoutManager

Размещает элементы списка в виде сетки одинакового размера.

У класса GridLayoutManager есть два конструктора. Для использования первого конструктора необходимы два параметра: контекст и количество столбцов в сетке.

Для второго конструктора — четыре параметра:

  • контекст;
  • количество столбцов в сетке;
  • ориентация списка — задаётся с помощью констант HORIZONTAL и VERTICAL класса LinearLayoutManager ;
  • булево значение — если передать true — список будет установлен в обратном порядке (начало будет в конце).

Если задать горизонтальную ориентацию, то в списке будет столько рядов, сколько было задано вторым параметром (в данном примере = 3), а листаться, само собой, будет в бок.

То же самое можно задать с помощью XML атрибутов:

StaggeredGridLayoutManager

Размещает элементы в виде неравномерной сетки.

У класса StaggeredGridLayoutManager всего один конструктор с двумя параметрами:

  • количество столбцов в сетке;
  • ориентация списка — задаётся с помощью констант HORIZONTAL и VERTICAL класса StaggeredGridLayoutManager .

Если задать горизонтальную ориентацию, то в списке будет столько рядов, сколько было задано первым параметром (в данном примере = 3), а листаться, само собой, будет в бок.

То же самое можно задать с помощью XML атрибутов:

Динамическое переключение

Переключаться между LayoutManager ‘ами можно динамически. Например, при нажатии на кнопку:

SnapHelper

SnapHelper позволяет настроить “прилипание” элементов к определённой позиции в RecyclerView . Например, при пролистывании можно настроить прилипание таким образом, что первый видимый элемент будет сам прилипать к краю экрана или ближайший к центру элемент будет автоматически вставать в центр экрана.

Существует два стандартных класса для работы с прилипанием элементов: LinearSnapHelper и PagerSnapHelper .

LinearSnapHelper застовляет ближайший к центру элемент вставать в центр экрана. Допустим вы листаете список и в какой-то момент убрали пальцы от экрана. Список без вашего участия автоматически прокрутится и установит в центр экрана ближайший элемент.

PagerSnapHelper предназначен для полноэкранных элементов и ведёт себя как ViewPager .

Добавить себе в проект просто:

Если ни один вариант вас не устраивает, то создайте свою собственную реализацию этих классов и опишите в нёй необходимое поведение элементов при пролистывании списка.

Использование нескольких макетов для элементов RecyclerView

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

  • требуется добавить header или footer;
  • отображение двух списков в одном RecyclerView ;
  • выделение определённого элемента в списке.

ViewType

Одним из способов, который используется для выделения элементов в списке, является присвоение viewType каждому объекту viewHolder . ViewType — это произвольное цифровое значение от 0 и выше, которое необходимо для того, чтобы различать объекты viewHolder между собой. Например, если вам требуется добавить header и footer, при этом элементы списка должны выглядеть идентично, то у вас будет три viewType : для header’а, footer’а и элемента списка.

Для начала добавьте в папку res/layout три макета: для header’а, footer’а и элемента списка.

В классе адаптера создадим константы, которые будут хранить значения viewType .

Для удобства создадим базовый класс GenericViewHolder .

От него будут наследоваться три класса ViewHolder . Каждый из них отвечает за свой макет и привязку к нему данных.

Обратите внимание на класс ListItemViewHolder . В отличии от остальных он является внутренним (модификатор inner ), так как ему для привязки данных требуется обращаться к свойству trees своего внешнего класса. Из поступившего номера позиции вычитается единица, так как нулевая позиция занята header’ом и не будет сюда поступать.

Теперь возьмёмся за код самого адаптера. С помощью метода getItemViewType() зададим viewType каждому объекту viewHolder в зависимости от его позиции в списке. Первый и последний элемент списка — это header и footer. Если в списке 15 элементов, то позиция для footer’а будет 15 + 1, так как header всегда находится в нулевой позиции.

В методе onCreateViewHolder() создаём объект viewHolder в зависимости от viewType .

В методе onBindViewHolder() вызываем метод привязки данных bindView() , который переопределён во всех наших классах ViewHolder .

Метод getItemCount() должен возвращать количество элементов в RecyclerView . Поэтому следует учесть наличие header’а и footer’а.

Адаптер готов к использованию. Результат будет примерно таким:

Несколько списков в одном RecyclerView

У меня как-то возникала необходимость отображения двух списков на одном экране друг за другом. При этом при клике по элементу из первого списка он должен был переместиться во второй список и наоборот.

Первая появившаяся мысль — добавить на экран два RecyclerView . И это вполне себе работает. Но возникает ряд неудобств, одно из них — некорректная работа overScroll. Эффект overScroll визуально показывает, что вы дошли до конца или начала списка.

Читайте также:  Вконтакте режим невидимки для андроид

И если на экране два RecyclerView , то эффект overScroll появляется для каждого из них. Выглядит не очень красиво. Конечно можно overScroll эффект отключить, но тогда появляется неуверенность: “А дошел ли я до конца списка?”.

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

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

Константы для хранения значений viewType :

Также используем для удобства базовый класс GenericViewHolder .

От него будут наследоваться два класса ViewHolder : один для элементов первого списка, второй для элементов второго списка.

Каждый из них будет по своему реализовывать метод bindView() . Для элементов первого списка ничего рассчитывать не требуется, нужно просто привязать к объектам viewHolder данные по порядку.

Второй список должен отображаться сразу после первого. Так как номер позиции может быть любым числом от 0 до list1.size + list2.size , в методе bindView() класса SecondListItemViewHolder потребуется произвести расчеты.

Также обратите внимание что оба класса являются внутренними (модификатор inner ), так как им для привязки данных требуется обращаться к компонентам адаптера list1 и list2 .

Переходим к коду адаптера. В методе getItemViewType() нужно предусмотреть все возможные сценарии: когда в обоих списках есть элементы и когда в одном из списков нет элементов.

В методе onCreateViewHolder() создаём объект viewHolder в зависимости от viewType .

В методе onBindViewHolder() вызываем метод привязки данных bindView() , который переопределён во всех наших классах ViewHolder , а также вешаем слушателя.

Метод getItemCount() должен возвращать количество элементов в RecyclerView . Поэтому следует учесть наличие двух списков.

При клике по элементу из первого или второго списка будет вызван метод updateUi() , который отмечает, что по элементу кликнули и переносит его в другой список.

Адаптер готов к использованию. Результат будет примерно таким:

Необязательно делать списки динамическими, таким образом можно отображать и статические списки. И даже комбинировать с предыдущим примером — добавлять header (один или для всех списков) и footer.

ConcatAdapter

Несмотря на то, что все примеры, описанные в предыдущем разделе, вполне себе рабочие, в плане кода выглядят не очень хорошо. В основном из-за того, что в одном адаптере скапливается множество реализаций класса ViewHolder , а также логика их отображения. Если нам понадобится добавить или удалить какой-либо ViewHolder , то придётся переписывать класс адаптера и заново его тестировать.

По этой причине в recyclerview:1.2.0-alpha02 был добавлен новый класс MergeAdapter , который в версии recyclerview:1.2.0-alpha04 переименовали в ConcatAdapter .

ConcatAdapter позволяет отображать содержимое нескольких адаптеров в одном RecyclerView . То есть вместо накапливания множества реализаций класса ViewHolder в одном адаптере, мы можем создать для каждого ViewHolder ‘а свой адаптер, а потом объединить их все при помощи ConcatAdapter . Таким образом код станет более понятным и переиспользуемым, а если потребуется добавить в RecyclerView что-то новое — просто создадим новый адаптер.

Использование ConcatAdapter . Обзор некоторых методов класса

Передайте в конструктор ConcatAdapter все ваши адаптеры, которые нужно объединить, чтобы отображать их в одном RecyclerView .

Адаптеры будут отображаться на экране в том порядке, в котором были переданы в конструктор класса ConcatAdapter .

Если один из адаптеров должен несколько раз отображаться на экране, то создайте несколько объектов этого адаптера и передайте их все в конструктор класса ConcatAdapter .

Когда мы вызываем метод notifyDataSetChanged() в любом из адаптеров, ConcatAdapter тоже его вызывает.

У класса ConcatAdapter есть конструктор, который позволяет передавать список из адаптеров. На экране они будут отображаться в том порядке, в котором были добавлены в список.

Если вам нужно добавить один из адаптеров не сразу, а позже, то используйте метод addAdapter() . Этот метод добавляет адаптер в последнюю позицию, т.е. отображаться он будет после всех остальных.

Если же требуется добавить адаптер не последним, а в определённую позицию, то в метод addAdapter() передайте номер позиции и сам адаптер. Метод добавит адаптер в указанную позицию, а все остальные адаптеры сместятся.

Обратите внимание, что номер позиции не может быть больше количества адаптеров (отсчёт начинается с нуля). В примере у нас три адаптера, каждому из которых может быть присвоена позиция 0, 1 или 2. Если указать число выше, то вылетит ошибка.

Для удаления адаптера используется метод removeAdapter() .

Чтобы узнать сколько элементов объединил в себе ConcatAdapter вызовите метод itemCount . Количество элементов суммируется со всех добавленных адаптеров.

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

Обычно если в адаптере нам надо обратиться к какой-либо позиции, мы используем метод getAdapterPosition() класса ViewHolder . При работе с ConcatAdapter вместо getAdapterPosition() следует использовать getBindingAdapterPosition() .

Возьмём пример, который был в разделе ViewType: требуется отобразить header, footer и список между ними. В таком случае у нас будет три адаптера: для header’а, footer’а и элементов списка. Можно использовать и два адаптера, если логика и внешний вид header’а и footer’а идентичны. Но для наглядности в своём примере я буду использовать три.

Для начала убедитесь, что в build.gradle добавлена нужная версия библиотеки recyclerView:

Можно использовать и версию 1.2.0-alpha02 , но учтите, что в этой версии ConcatAdapter ещё носит название MergeAdapter .

Создадим классы данных для header’а, footer’а и элементов списка (деревья).

Добавим макет для каждого компонента.

За отображение header’а будет отвечать HeaderAdapter .

Для отображения элементов списка создадим ListItemAdapter .

Ну и наконец адаптер для отображения footer’а.

Теперь осталось лишь объединить всё вместе в методе onCreate() — для активити или в методе onViewCreated() — для фрагмента. Для этого создадим по одному объекту каждого из адаптеров и передадим их классу ConcatAdapter() в том порядке, в котором они должны быть отражены на экране.

Если же в ConcatAdapter() передать footer сразу после header’а

то результат будет таким:

Полезные ссылки

Общие ссылки по теме:
Create a List with RecyclerView — гайд из официальной документации.
RecyclerView — документация по классу (androidx).
Recyclerview — Release Notes — информация о выходе новых версий.
Using the RecyclerView — гайд от codepath.

Кастомизация:
Having multiple lists in a single RecyclerView — гайд по использованию нескольких списков в одном RecyclerView .

Адаптеры:
ConcatAdapter — официальная документация.

Код:
RecyclerView — полный код всех примеров из данной статьи.
MergeAdapter-sample — пример реализации ConcatAdapter от Kotlin Android Open Source.
Concat Adapter Android Example — пример реализации ConcatAdapter от Mindorks.

Источник

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