- О RecyclerView и выделении элементов
- Содержание
- 1. Немного о ViewHolder’ах
- 2. Вкратце о RecyclerView
- 3. Выделяем элементы
- 4. Заключение + Бонус
- Реализация списка с заголовком, футером и пагинацией в Андроид
- RecyclerView
- Настройка Gradle
- Добавление RecyclerView в XML представление
- Привязка XML с классом JAVA
- RecyclerView ItemDecoration
- RecyclerView Adapter
- Пагинация
- Создание сложного RecyclerView за 20 минут в Android на базе Groupie
- Создание проекта и добавление библиотек.
- Создание ячеек для отображения контента
- Создание главной ячейки с вложенным RecyclerView
- Ячейка для фильма
- Квадратная ячейка для отображения обложки игры
- Все вместе. Соединяем все ячейки вместе
О 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. Так в чём же его отличия, спросите вы?
Я приведу вкратце основные, а для более полного их раскрытия советую к ознакомлению вот эту статью на хабре. Итак:
- Сам виджет больше не берёт на себя обязанность по размещению элементов. Для этого появились LayoutManager’ы.
- Паттерн ViewHolder стал обязательным. Причём виджет научился заново использовать уже созданные ViewHolder’ы и удалять уже не используемые (отсюда и название), что благоприятно сказывается на быстродействии и размере используемой памяти.
- Новый, удобный способ работы с анимацией.
Я попробовал его и виджет оставил у меня противоречивые впечатления. С одной стороны, да, здорово, что теперь использование ViewHolder’а является обязательным, работает вроде тоже быстрей, памяти жрёт меньше. С другой стороны, есть проблемы со сложностью и недоделанностью виджета.
Что я понимаю под сложностью? Если что-то не работало в ListView (или работало не так как задумано) всегда можно было залезть в исходники, разобраться, в чём ошибка, исправить её, подоткнуть костылей тут и там и всё начинало работать. RecyclerView гораздо сложнее в плане логики работы, мозг сломаешь, пока разберёшься. Я пытался, но забросил, уж слишком много времени и сил для этого нужно.
Вторая проблема — банальное отсутствие функционала, присутствовавшего в ListView и GridView. За примерами далеко ходить не надо — стандартный функционал выделения элементов (дальнейшая тема этой статьи), отступы между элементами. Раньше, чтобы добавить всё это, нужно было написать буквально пару строчек кода, теперь на это уйдут уже десятки строк. Есть анимации, но только для добавления/удаления/редактирования элемента. Если же вы хотите, например, анимировать частичное изменение элемента, то к вам в дверь уже стучится птица обломинго. Виджет не поддерживает анимацию части элемента, и если анимировать элемент извне (из адаптера, например), то лучше этого не делать — подобные манипуляции оставляют элементы виджета (те самые ViewHolder’ы) в неопределённом состоянии, что приводит к совершенно фантастическому поведению вашего списка.
Резюмируя — если у вас в проекте используются старые виджеты и вы не используете анимации, то лучше пока оставить всё как есть и дождаться, когда виджет наполнят отсутствующим функционалом. Если же вы хотите простые анимации и при этом взаимодействие пользователя с виджетом подразумевается простое — попробуйте RecyclerView, вдруг понравиться.
3. Выделяем элементы
Итак, перейдём к главному — к технической части статьи. Поговорим о том, как выделять элементы в RecyclerView. Сразу оговорюсь — идея реализации почерпнута из замечательной серии статей Билла Филлипса про RecyclerView (ссылки в конце), так что всё нижеследующее можно считать вольным кратким пересказом.
В ListView для выделения элементов использовался метод setChoiceMode(int), RecyclerView же понятия не имеет, что элементы могут выделяться, поэтому мы должны научить этому наш адаптер.
Схема такая:
На диаграмме я схематично обозначил связи между объектами. Пунктирные стрелки — ссылки, остальные — вызовы методов. Зелёным я обозначил объекты, которые непосредственно реализуют логику выделения.
Принцип работы получается следующий:
- ViewHolderWrapper устанавливает себя в качестве ClickListener’а для корневой вьюхи ViewHolder’а и начинает получать события onClick и onLongClick. В зависимости от реализации он может просто проксировать эти события в HolderClickObservable (ViewHolderClickWrapper), либо, исходя из текущего статуса SelectionHelper’а выделять элемент вызовом setItemSelected(ViewHolder, boolean) (ViewHolderMultiSelectionWrapper).
- SelectionHelper сохраняет информацию о выделенных элементах и оповещает слушателей (SelectionObserver) об изменении выделения.
- Слушатель (в нашем случае адаптер) отвечает за визуальное отображение выделения элемента, а также взаимодействия с ним (на диаграмме вызов 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’ы.
Выглядит это примерно так:
- Открываем приложение. На экране 10 элементов — 10 ViewHolder’ов создано для них.
- Запускаем ActionMode, начинаем выделять элементы — 1,2,3.
- Прокручиваем вьюху вниз, видим элементы с 10 по 20. Думаете, что теперь в памяти висит 20 ViewHolder’ов? Как бы ни так! Для части данных RecyclerView создаст новые ViewHolder’ы, а для другой заново использует уже имеющиеся. Причём неизвестно в каком порядке.
- Теперь если мы прокрутим вьюху обратно вверх, часть из наших 10 ViewHolder’ов будет уничтожена, вместо них будут созданы новые. Оставшаяся часть будет использована заново и совершенно не обязательно для тех же позиций.
- Отменяем 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
RecyclerView — это расширенная версия ListView с некоторыми улучшениями в производительности и с новыми функциями. Как следует из названия, RecyclerView перерабатывает или повторно использует представления элементов при прокрутке. В RecyclerView гораздо проще добавлять анимации по сравнению с ListView. В этом уроке мы разберем, как создать RecyclerView с заголовком, футером, разбиением на страницы и анимацией.
Настройка Gradle
Добавьте следующую зависимость в файл build.gradle:
Добавление RecyclerView в XML представление
После того, как проект будет синхронизирован, добавьте компонент RecyclerView в ваш макет:
Привязка XML с классом JAVA
Теперь в методе onCreate вашей активности добавьте следующий код:
Прежде чем идти дальше, давайте подробно рассмотрим приведенный выше код
- Layout Manager — Простыми словами, Layout Manager помогает нам определить структуру нашего RecyclerView. Есть три встроенных Layout Managers. Помимо этого, мы можем создать собственный пользовательский Layout Manager, чтобы удовлетворить наши требования.
- LinearLayoutManager показывает элементы в списке с вертикальной или горизонтальной прокруткой.
- GridLayoutManager показывает элементы в сетке.
- StaggeredGridLayoutManager показывает элементы в шахматной сетке.
RecyclerView ItemDecoration
ItemDecoration позволяет приложению добавлять специальный полосы и смещения к определенным представлениям элементов из набора данных адаптера. Это может быть полезно для рисования разделителей между элементами, выделениями, границами визуальной группировки и т. д. – developer.android.com
В этом примере мы будем использовать ItemDecoration для добавления отступов к каждому элементу.
В вышеприведенном классе мы устанавливаем отступы к нулевому элементу.
RecyclerView Adapter
Теперь давайте настроим адаптер ReeyclerView с заголовком и футером.
Пагинация
Теперь, когда адаптер готов, давайте посмотрим, как добавить пагинацию в список RecyclerView. Это довольно легко сделать и должно быть добавлено в onCreate после установки Adapter to Recycler-View.
Всякий раз, когда данные изменяются в mList, вызывайте функцию ниже, чтобы обновить адаптер RecyclerView и показать новые данные.
Надеюсь, что этот пост поможет вам получить общее представление о настройке RecyclerView с заголовком, подвалом и пагинацией.
Источник
Создание сложного RecyclerView за 20 минут в Android на базе Groupie
Списки являются основным способом представления различного контента в мобильных приложениях. Будь то социальная сеть, приложение для чтения книг или интернет-магазин, в большинстве таких приложений встречаются списки с разными видами ячеек, разного уровня вложенности.
Естественно, чтобы поддержать такое разнообразие контента и при этом сохранить оптимальную производительность для такой задачи лучше всего использовать RecyclerView. Как создать список, поддерживающий разные виды ячеек, которые в свою очередь могут тоже содержать вложенные списки за 20 минут, я покажу на примере в этой статье. В конце у вас получится вот такой список:
Итак, задача: создать список для отображения различного вида контента, при этом каждая категория, то есть ячейка списка может содержать неограниченное количество более мелких ячеек и иметь горизонтальный скрол. Звучит сложно? Если вы думаете что это сложная задача, над которой вам нужно будет работать всю неделю, то спешу вас обрадовать, мы сделаем такой список примерно на полчаса.
Подходов к решению такой задачи, множество, но суть решения одна — здесь необходимо использовать RecyclerView с различными типами ячеек, в которых также находится RecyclerView для возможности горизонтального скролла неограниченного количества ячеек. Можно использовать как стандартный подход, в котором необходимо будет создать adapter для каждого из списков, ViewHolders для разного типа ячеек и так далее. А можно использовать более быстрый подход без множества похожего кода на базе библиотеки Groupie
Groupie is a simple, flexible library for complex RecyclerView layouts.
Это простая и в тоже время мощная библиотека для упрощения построения списков на базе RecyclerView, которая заметно ускорит разработку сложных списков как в примерах выше.
Безусловно, все что мы видели можно сделать и без этой библиотеки, тем более совсем недавно в Android появился MergeAdapter, о котором я писал в этой статье. Но так или иначе вы столкнётесь с недостатками стандартного подхода, описанного в той же статье. Поэтому, сегодня мы попробуем новый подход, ускоряющий разработку, избавляющий от написания бойлерплейт-кода и соответсвующий принципам SOLID.
Если кратко, то алгоритм действий выглядит следующим:
- Создаём проект. Добавляем нужные зависимости.
- Определяем нужные ячейки. Создаём layouts для отображения UI
- Соединяем ячейки с адаптером RecyclerView и наслаждаемся результатом.
Создание проекта и добавление библиотек.
Для создания списка как в примере на картинке нам понадобится 4 библиотеки: RecyclerView, CardView, Picasso (для отображения картинок) и Groupie. Добавим всё это в build.gradle(app):
Кроме этого, добавьте в build.gradle в блок android
Нажмите Sync Now для скачивания необходимых зависимостей.
Создание ячеек для отображения контента
Для отображения списка нам понадобится 3 типа ячеек:
- Общая ячейка — контейнер для отображения вложенного списка. Обозначена красным прямоугольником.
- Ячейка внутри основной ячейки для отображения информации о фильме. Такие ячейки выделены синим прямоугольником. Они находятся внутри основной ячейки в RecyclerView c горизонтальным скролом.
- Квадратная ячейка для отображения обложек игр. Выделена зелёным цветом.
Создание главной ячейки с вложенным RecyclerView
Вначале создадим общую ячейку с вложенным RecyclerView для отображения более мелких ячеек.
Вёрстка такой ячейки будет состоять из CardView с LinearLayout для отображения названия, описания и RecyclerView для отображения внутренних ячеек.
Теперь создадим логику для отображения данных ячейки.
Каждая ячейка при использовании Groupie должна быть наследником от абстрактного класса Item. Для этого необходимо переопределить всего 2 метода getLayout() и bind(). То есть для создания ячейки вам нужно указать layout который будет использоваться для отображения UI и дописать логику формирования данных для этой ячейки и всё! Теперь не нужно писать однотипные адаптеры для разных ячеек или комбинировать множество разных типов ячеек в одном адаптере, нарушая принципы SOLID. Ну или выдумывать базовые классы для ячеек, только для того, чтобы можно было переиспользовать один и тот же адаптер. C Groupie для каждой ячейки вам необходимо создать свой класс, и описать в нем UI!
В данном пример мы будем использовать одну общую ячейку, которая на вход принимает название, описание и список других Item, то есть ячеек, которые и будут наполнять вложенный в эту ячейку RecyclerView. Самое интересное тут, пожалуй вот эта строчка:
То есть для RecyclerView который внутри этой ячейки, необходимо добавить общий GroupAdapter и наполнить его ячейками, которые являются наследниками Item.
Общий контейнер готов, теперь осталось сверстать частные ячейки для каждого типа контента. Их будет 2:
- Ячейка для фильма с названием фильма
- Квадратная ячейка с обложкой игры
Ячейка для фильма
Ячейка для фильма, также должна быть наследником Item и должна реализовать 2 метода:
Верстка достаточно простая и код можно посмотреть в проекте на GitHub.
Квадратная ячейка для отображения обложки игры
Эта ячейка тоже является достаточно простой, поэтому лучше посмотрите код проекта.
Все вместе. Соединяем все ячейки вместе
Для создания списка теперь нужно создать ячейки с контентом и передать их в адаптер RecyclerView. Для создания ячеек были созданы 2 метода getPopularMovies() и getPopularGames() которые возвращают ячейки типа Item.
Каждый из методов возвращает 1 ячейку MainCardContainer — которой передаётся в качестве аргумента список ячеек уже с контентом для вложенного RecyclerView. Например, для ячейки которая отображает список фильмов нужно указать список ячеек MovieItem. Для второй ячейки, которая отображает список игр — мы создадим также метод, который создаст основную общую ячейку и передаст ячейки с играми.
В итоге создание списка теперь будет выглядеть так:
Последняя строка как раз использует GroupAdapter в который мы можем передать любые ячейки, которые являются наследниками Item.
Ну вот и всё! Буквально за 30 минут мы создали сложный список для отображения различного типа контента с вложенным горизонтальным списком! Сравните такой подход с традиционным и сделайте выводы сами! Абсолютно точно такой подход сэкономит вам время на разработку подобных UI — компонентов и избавит от кучи бесполезного кода. Хотите узнать ещё больше продвинутых фишек? Успейте записаться на онлайн-интенсив по Android-разработке
Понравилась статья? Не забудь подписаться и поставить лайк, а ещё
Источник