- использование notifyItemRemoved или notifyDataSetChanged с RecyclerView в Android
- 8 ответов
- RecyclerView
- getItemCount()
- onCreateViewHolder
- onBindViewHolder()
- Горизонтальная прокрутка
- Оптимизация
- RecyclerView на максималках: разбор библиотек
- Глава первая, в которой заказчик мечтает о приложении, а мы — о четких требованиях
- Итоги
- Что не так с нашими адаптерами?
- Глава вторая, в которой все могло быть иначе
- Глава третья, в которой разработчик снимает розовые очки, сравнив библиотеки
- AdapterDelegates
- Плюсы:
- Минусы:
- Groupie
- Плюсы:
- Минусы:
- Epoxy
- Плюсы:
- Минусы:
- Итоги
- Так, а что выбрать-то?
использование notifyItemRemoved или notifyDataSetChanged с RecyclerView в Android
Я создаю список карт для отображения с помощью RecyclerView, где каждая карта имеет кнопку для удаления этой карты из списка.
когда я использую notifyItemRemoved() чтобы удалить карту в RecyclerView, он удаляет элемент и анимирует штраф, но данные в списке не обновляются правильно.
если вместо этого я переключился на notifyDataSetChanged() затем элементы в списке удаляются и обновляются правильно, но затем карты не анимируются.
кто-нибудь имеет опыт использования notifyItemRemoved() и знает, почему он ведет себя иначе, чем notifyDataSetChanged?
вот некоторые фрагменты кода, которые я использую:
8 ответов
использовать notifyItemRangeChanged (позиция, getItemCount ()); после notifyItemRemoved (позиция);
Вам не нужно использовать индекс, просто используйте позицию. См. код ниже.
и работает как шарм.
моя ошибка , notifyItemChanged(должность) беспомощен, элемент позиции может быть удален ,и элемент позиции+1 в порядке, но элементы начинаются с позиции+2, Вы получите исключение, пожалуйста, используйте notifyItemRangeChanged (позиция, getItemCount ()); после notifyItemRemoved(должность);
Как предположил @pskink, это должно было быть (индекс+1) в моем случае с notifyItemRemoved(index+1) , вероятно, потому, что я резервирую верхний индекс, т. е. position=0 для заголовка.
можно использовать getLayoutPosition() С RecyclerView.ViewHolder
getLayoutPosition() обеспечивает точное положение элемента в макет и код
в моем случае я использую Content Provider и пользовательский адаптер RecyclerView с курсором. Эта строка кода, где вы сообщите:
предполагая, что в вашем адаптере recyclerView (кнопка удаления):
и в вашей базе данных провайдера:
вы должны добавить remove listener в класс ViewHolder
Источник
RecyclerView
Компонент RecyclerView появился в Android 5.0 Lollipop и находится в разделе Containers. Для простоты буду называть его списком, хотя на самом деле это универсальный элемент управления с большими возможностями.
Раньше для отображения прокручиваемого списка использовался ListView. Со временем у него обнаружилось множество недостатков, которые было трудно исправить. Тогда решили создать новый элемент с нуля.
Вначале это был сырой продукт, потом его доработали. На данном этапе можно считать, что он стал полноценной заменой устаревшего ListView.
Схематично работу RecyclerView можно представить следующим образом. На экране отображаются видимые элементы списка. Когда при прокрутке списка верхний элемент уходит за пределы экрана и становится невидимым, его содержимое очищается. При этом сам «чистый» элемент помещается вниз экрана и заполняется новыми данными, иными словами переиспользуется, отсюда название Recycle.
Компонент RecyclerView не является родственником ListView и относится к семейству ViewGroup. Он часто используется как замена ListView, но его возможности шире.
Следует сказать, что при работе с ним приходится писать много кода, который пугает новичков. Если с RecyclerView работать не постоянно, то порой забываются детали и сложно вспомнить необходимые шаги. Многие просто сохраняют отдельные шаблоны и копируют в разные проекты.
Внешний вид можно представить не только в виде обычного списка, но и в виде сетки. При необходимости можно быстро переключиться между разными типами отображения.
Для размещения своих дочерних элементов используется специальный менеджер макетов LayoutManager. Он может быть трёх видов.
- LinearLayoutManager — дочерние элементы размещаются вертикально (как в ListView) или горизонтально
- GridLayoutManager — дочерние элементы размещаются в сетке, как в GridView
- StaggeredGridLayoutManager — неравномерная сетка
Можно создать собственный менеджер на основе RecyclerView.LayoutManager.
RecyclerView.ItemDecoration позволяет работать с дочерними элементами: отступы, внешний вид.
ItemAnimator — отвечает за анимацию элементов при добавлении, удалении и других операций.
RecyclerView.Adapter связывает данные с компонентом и отслеживает изменения.
- notifyItemInserted(), notifyItemRemoved(), notifyItemChanged() — методы, отслеживающие добавление, удаление или изменение позиции одного элемента
- notifyItemRangeInserted(), notifyItemRangeRemoved(), notifyItemRangeChanged() — методы, отслеживающие изменение порядка элеметов
Стандартный метод notifyDataSetChanged() поддерживается, но он не приводит к внешнему изменению элементов на экране.
Программисты со стажем знают, что для создания «правильного» ListView нужно было создавать класс ViewHolder. В старых списках его можно было игнорировать. Теперь это стало необходимым условием.
Общая модель работы компонента.
Мы рассмотрим только базовый пример для быстрого знакомства. В реальных проектах примеры будут гораздо сложнее, чтобы обеспечить другие функциональные возможности — обработка жестов, анимация, динамическое удаление и добавление элементов.
Размещаем компонент в макете экрана через панель инструментов. Но сначала добавим зависимость.
Создадим макет для отдельного элемента списка. Варианты могут быть самыми разными — можно использовать один TextView для отображения строк (имена котов), можно использовать связку ImageView и TextView (имена котов и их наглые морды). Мы возьмём для примера две текстовые метки. Создадим новый файл res/layout/recyclerview_item.xml.
Добавим компонент в разметку экрана активности.
Минимальный код для запуска.
Пока ничего сложного, но выводить такой список ничего не будет. Нужен адаптер и данные для отображения. В адаптере описывается способ связи между данными и компонентом.
Начнём по порядку, чтобы запомнить последовательность. Для начала создадим обычный класс и в конструкторе передадим список строк. Список будет содержать имена котов.
Класс MyViewHolder на основе ViewHolder служит для оптимизации ресурсов. Новый класс добавим в состав нашего созданного ранее класса.
В созданном классе нужно просто перечислить используемые компоненты из макета для отдельного элемента списка. В нашем примере задействованы два TextView, инициализируем их через идентификаторы.
Создадим адаптер — наследуем наш класс от класса RecyclerView.Adapter и в качестве параметра указываем созданный нами MyViewHolder. Студия попросит реализовать три метода.
getItemCount()
Как правило данные являются однотипными, например, список или массив строк. Адаптеру нужно знать, сколько элементов нужно предоставить компоненту, чтобы распределить ресурсы и подготовиться к отображению на экране. При работе с коллекциями или массивом мы можем просто вычислить его длину и передать это значение методу адаптера getItemCount(). В простых случаях мы можем записать код в одну строчку.
onCreateViewHolder
В методе onCreateViewHolder нужно указать идентификатор макета для отдельного элемента списка, созданный нами ранее в файле recyclerview_item.xml. А также вернуть наш объект класса ViewHolder.
onBindViewHolder()
В методе адаптера onBindViewHolder() связываем используемые текстовые метки с данными — в одном случае это значения из списка, во втором используется одна и та же строка.
Должно получиться следующее.
Подключаем в активности. Создадим пока бессмысленный список строк, который передадим в адаптер.
Запускаем ещё раз.
Вариант с числами нам не интересен, поэтому добавим котов. Имена котов и кошек разместим в ресурсах в виде массива в файле res/values/strings.xml.
Создадим новую функцию для получения списка котов из ресурсов и передадим его адаптеру.
Горизонтальная прокрутка
Можем указать горизонтальный вариант прокрутки. Остальной код менять не придётся.
А можно вообще обойтись только XML-декларацией.
Оптимизация
При прокрутке под капотом выполняется сложная работа по обновлению контента. Поэтому не следует перегружать элементы списка сложной структурой и не перебарщивайте с вложенными элементами, а также нужно следить за оптимизацией, чтобы избежать лагов.
При работе с изображениями старайтесь использовать готовые библиотеки Picasso, Glide, Fresco и т.д.
Если картинки загружаются с сервера, неплохо заранее вычислять их размеры и пропорции. В некоторых случаях желательно позаботиться, чтобы картинки были одного размера (если это возможно).
Не перегружайте лишним кодом метод onBindViewHolder(). Только самое необходимое.
Источник
RecyclerView на максималках: разбор библиотек
Илья Некрасов, Mahtalitet, android-разработчик KODE
За два с половиной года в андроид-разработке я успел поработать на совершенно разных проектах: от социальной сети для автомобилистов и латвийского банка до федеральной бонусной системы и третьей по перевозкам авиакомпании. Так или иначе в каждом из этих проектов я сталкивался с задачами, которые требовали поиска неклассических решений при реализации списков с помощью класса RecyclerView.
Эта статья — плод подготовки к выступлению на DevFest Kaliningrad’18, а также общения с коллегами — особенно будет полезна начинающим разработчикам и тем, кто использовал лишь одну из существующих библиотек.
Для начала копнем немного в суть вопроса и источника боли, а именно — разрастание функционала при разработке приложения и усложнения используемых списков.
Глава первая, в которой заказчик мечтает о приложении, а мы — о четких требованиях
Давайте представим ситуацию, когда в студию обращается заказчик, который хочет мобильное приложение магазина по продаже резиновых уточек.
Проект быстро развивается, новые идеи возникают регулярно и не оформлены в долгосрочный roadmap.
Сперва заказчик просит нас показывать список существующих товаров и при клике оформлять заявку на доставку. За решением ходить далеко не нужно: пользуемся классическим набором из RecyclerView, простым самописным адаптером для него и Activity.
Для адаптера мы используем однородные данные, один ViewHolder и простую логику биндинга.
Со временем у заказчика появляется идея добавить к резиновым уточкам другую категорию товаров, а значит, придется добавить новую модель данных и новый макет. Но самое главное, что в адаптере появится еще один ViewType, с помощью которого можно определить, какой ViewHolder использовать для конкретного элемента списка.
После этого к категориям добавляются заголовки, по которым каждую категорию можно свернуть и развернуть для упрощения ориентации пользователей в магазине. Это плюс ещё один ViewType и ViewHolder для заголовков. Кроме того, придется усложнить адаптер, так как надо хранить список открытых групп и с помощью него проверять необходимость скрытия и отображения того или иного элемента по нажатию на заголовок.
Думаю, суть вы улавливаете — такое нагромождение мало напоминает здоровую разработку. А впереди все новые и новые требования от заказчика: закрепить рекламный баннер в начале списка, реализовать возможность выбора количества заказываемых уточек. Только эти задачи в конечном счете превратятся в очередные адаптеры, которые вновь придется писать с нуля.
Процесс разработки классического адаптера в истории гитхаба
Итоги
По факту картина совсем не воодушевляет: отдельные адаптеры приходится затачивать под конкретные кейсы. Мы все понимаем, что в реальном приложении таких экранов-списков бывают десятки, а то и сотни. И содержат они не информацию об уточках, а более сложные данные. Да еще и дизайн у них намного сложнее.
Что не так с нашими адаптерами?
Глава вторая, в которой все могло быть иначе
Представить развитие приложения на годы вперед нереально, да и бессмысленно. После пары таких танцев с бубном как в прошлой главе и написания десятков адаптеров у любого возникнет вопрос “Может, есть другие решения?”.
Прошерстив Github, обнаруживаем, что еще в 2015 году появилась первая библиотека AdapterDelegates, а уже через год арсенал разработчиков пополнили Groupie и Epoxy — все они помогают облегчить жизнь, но в каждой есть своя специфика и подводные камни.
Есть еще несколько похожих библиотек (например, FastAdapter), но ни я, ни мои коллеги с ними не работали, поэтому не будем их рассматривать в статье.
Прежде чем сравнивать библиотеки, кратко разберем вышеописанный кейс с онлайн-магазином при условии использования AdapterDelegates — из разбираемых библиотек она наиболее простая с точки зрения внутренней реализации и использования (впрочем, она не во всем продвинутая, поэтому многое приходится дописывать руками).
Библиотека от адаптера нас полностью не избавит, но он будет формироваться из блоков (кирпичиков), которые мы безболезненно можем добавлять в список или убирать из него и менять их местами.
Уже с первой задачи мы видим разницу: у нас появляется класс адаптера, который наследуется от библиотеки. И в дополнение — тот самый кирпичик, который называется делегатом и от которого мы также наследуемся и реализуем часть необходимой нам логики. Дальше мы добавляем делегат в менеджер — это тоже класс библиотеки. И последнее, что нужно — создать адаптер и заполнить его данными
Для реализации второй категории магазина и заголовков напишем еще пару делегатов, а анимация появляется благодаря классу DiffUtil.
Здесь обозначу краткий, но категоричный вывод: использование даже этой библиотеки решает все перечисленные проблемы, которые возникали у нас при усложнении приложения в кейсе с онлайн-магазином, но без минусов никуда, и о них дальше.
Процесс разработки адаптера с AdapterDelegates в истории гитхаба
Глава третья, в которой разработчик снимает розовые очки, сравнив библиотеки
Погрузимся подробнее в функционал и работу каждой из библиотек. Все три библиотеки я так или иначе применял на наших проектах, в зависимости от задач и сложности приложения.
AdapterDelegates
Эту библиотеку мы используем в приложении одной из крупнейших российских авиакомпаний. Нам потребовалось заменить простой список оплаты на список с группами и большим количеством различных параметров.
Упрощенно схема работы библиотеки выглядит вот так:
Основной класс — это DelegateAdapter, различные “кирпичики” — это “делегаты”, которые отвечают за отображение определенного типа данных и, конечно, сам список.
Плюсы:
Минусы:
В целом, эта библиотека решает все основные сложности при расширении функционала приложения и подойдет тем, кто ранее библиотеки не применял. Но останавливаться только на ней я не советую.
Groupie
Groupie, созданную несколько лет назад Lisa Wray, мы используем часто, в том числе полностью с помощью нее написали приложение для одного латвийского банка.
Для того, чтобы использовать эту библиотеку, в первую очередь нужно разобраться с зависимостями. В дополнение к основной можно использовать несколько вариантов на выбор:
Останавливаемся на чём-то одном и прописываем необходимые зависимости.
На примере онлайн-магазина с уточками нам нужно создать Item, унаследованный от класса библиотеки, указать макет и реализовать биндинг через котлиновские синтентики. Если сравнивать с количеством кода, который пришлось написать с AdapterDelegates, это просто небо и земля.
Всё, что остается — задать в качестве адаптера RecyclerView GroupieAdapter, и положить в него смапленные айтемы.
Видно, что схема работы больше и сложнее. Здесь кроме простых айтемов можно использовать целые секции — группы айтемов и другие классы.
Плюсы:
Минусы:
Важно, что Groupie при всех своих минусах способна легко заменить AdapterDelegates, особенно если вы планируете делать сворачивающиеся списки первого уровня, и не хочется писать много бойлерплейта.
Epoxy
Последняя библиотека, которую мы стали применять сравнительно недавно — это Epoxy, разработанная ребятами из Airbnb. Библиотека сложная, но позволяет решать целый скоуп задач. Сами программисты Airbnb используют ее для рендера экранов прямо с сервера. Нам Epoxy пригодилась на одном из свежих проектов — приложении для банка в Екатеринбурге.
Чтобы разработать экраны, нам пришлось работать с разными видами данных, огромным числом списков. А один из экранов был прямо-таки нескончаемым. И с этим всем нам помогла справиться Epoxy.
Принцип работы библиотеки в целом похож на две предыдущие, за исключением того, что вместо адаптера для построения списка используется EpoxyController, который позволяет декларативно определить структуру адаптера.
Чтобы этого добиться, библиотека построена на кодогенерации. Как это работает — со всеми нюансами неплохо описано в wiki и отражено в семплах.
Плюсы:
Минусы:
Итоги
Главное, что я хотел донести: не стоит мириться со сложностью, которая появляется, когда нужно делать сложные списки и постоянно приходится их переделывать. А такое случается очень часто. Да и в принципе при их реализации, если проект только стартует, или вы занимаетесь его рефакторингом.
Реальность такова, что не стоит лишний раз усложнять логику, думая, что хватит каких-то собственных абстракций. Их хватает ненадолго А работать с ними не только не доставляет удовольствия, так еще и остается соблазн перенести часть логику в UI-часть, которойу там быть не должно. Есть инструменты, которые помогут избежать большинства проблем, и ими нужно пользоваться.
Понимаю, что для многих опытных (и не только) разработчиков это либо очевидно, либо они могут со мной не согласиться. Но считаю важным еще раз сделать на этом акцент.
Так, а что выбрать-то?
Советовать остановиться на одной библиотеке довольно трудно, потому что выбор зависит от многих факторов: от личных предпочтений до идеологии на проекте.
Я бы поступил следующим образом:
- Если вы только начинаете путь в разработке, попробуйте начать на небольшом проекте с AdapterDelegates — это самая простая библиотека, — особых знаний не потребуется. Поймете, как с этим работать и почему это удобнее, чем писать адаптеры самому.
- Groupie подойдет тем, кто уже наигрался с AdapterDelegates и ему надоело писать кучу бойлерплейта, либо всем остальным, кто сразу хочет начать с золотой середины. И не забываем про наличие сворачивающихся групп из коробки — это тоже неплохой аргумент в ее пользу.
- Ну и Epoxy — для тех, кто столкнулся с по-настоящему сложным проектом, с огромным количеством данных, так что сложность c жирностью библиотеки будет меньшей проблемой. Поначалу будет тяжело, зато дальше реализация списков покажется плевым делом. Важным аргументом в пользу Epoxy может стать наличие на проекте DataBinding‘а и MVVM — она буквально создана для этого, учитывая возможность генерации моделей из соответствующих макетов.
Если у вас остались вопросы, то можно заглянуть по ссылке, чтобы еще раз посмотреть код нашего приложения с уточками.
Источник