[Разработка под Android, Kotlin] MVVM и выбор элементов в адаптере — Базовый адаптер
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Итак, я наконец-то добрался до кульминации своей идеи с библиотекой, включающей в себя логику выбора элементов из списка в адаптере. После решения, независимого от платформы, и библиотеки, основанной на LiveData, я написал то, что поможет быстро и легко связать всё это с адаптером, чтобы сократить код в целом.
Интерфейс SelectingListAdapter
Начнём с простого интерфейса SelectingListAdapter, который я добавил для адаптера с обычным линейным списком. По моему опыту, где-то 90-95% адаптеров реализуются именно в таком виде.
interface SelectingListAdapter<T> {
fun setListItems(items: ArrayList<T>)
}
Ничего сложного — только обновление списка элементов. И, на мой взгляд, не составит труда добавить этот интерфейс к списку реализуемых в вашем адаптере. Зачем? Чтобы можно было использовать оба метода расширения, которые я укажу далее:
fun <T> SelectingListAdapter<T>.observeItemsChange(lifecycleOwner: LifecycleOwner,
liveDataSource: LiveDataSource<T>) {
liveDataSource.allItems.observe(lifecycleOwner, { items -> setListItems(items) })
}
Метод observeItemsChange подписывает на изменения списка элементов в LiveDataSource.
fun RecyclerView.Adapter<*>.observeSelectionChange(lifecycleOwner: LifecycleOwner,
liveDataSource: LiveDataSource<*>) {
liveDataSource.observeSelectionChange(lifecycleOwner) { position, _ ->
notifyItemChanged(position)
}
}
Метод observeSelectionChange уже подписывает на изменения в выборе элементов. Здесь происходит только оповещение о том, что элемент надо обновить. О проверке, выбран ли уже этот элемент, будет описано ниже.
fun <T, TAdapter> TAdapter.observeAllChanges(lifecycleOwner: LifecycleOwner,
liveDataSource: LiveDataSource<T>)
where TAdapter : RecyclerView.Adapter<*>,
TAdapter : SelectingListAdapter<T> {
observeSelectionChange(lifecycleOwner, liveDataSource)
observeItemsChange(lifecycleOwner, liveDataSource)
}
Ну и если хочется вызвать сразу оба метода, есть observeAllChanges, который сделает это одной строкой.
Класс BaseSelectingListAdapter
А вот для реализации базового поведения, в котором уже адаптер обращается к LiveDataSource, я написал абстрактный класс адаптера. Обращаю так же внимание, что под этот адаптер так же используется специальный абстрактный класс холдера:
abstract class BaseSelectingListAdapter<T, VH: BaseSelectingListHolder<T>>
: RecyclerView.Adapter<VH>(), SelectingListAdapter<T> {
var callback: SelectingListAdapterCallback? = null
...
}
abstract class BaseSelectingListHolder<T>(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bindItem(item: T, isSelected: Boolean, onClick: (() -> Unit)?)
}
Пока сосредоточимся на том, что же это за SelectingListAdapterCallback.
interface SelectingListAdapterCallback {
fun isItemSelected(position: Int): Boolean
fun clickItem(position: Int)
}
Это весьма простенький колбэк, на котором будет как раз определение, выбран ли запрашиваемый элемент, а так же на него будет перенаправлена обработка клика по элементу.
И в BaseSelectingListAdapter есть метод, чтобы создать этот самый колбэк на основании LiveDataSource.
fun <T> setCallback(liveDataSource: LiveDataSource<T>) {
callback = object : SelectingListAdapterCallback {
override fun isItemSelected(position: Int) =
liveDataSource.isItemSelected(position)
override fun clickItem(position: Int) {
liveDataSource.clickPosition(position)
}
}
}
В остальном класс BaseSelectingListAdapter представляет из себя обычную реализацию адаптера с линейным списком — настолько скучно, что даже не хочу этот код приводить здесь.
fun fullyInitialize(lifecycleOwner: LifecycleOwner,
liveDataSource: LiveDataSource<T>) {
observeAllChanges(lifecycleOwner, liveDataSource)
setCallback(liveDataSource)
}
Ну и метод fullyInitialize, который помимо установки колбэка ещё и сразу подписывает на все изменения. Лучше названия для него придумать так и не смог. Либо английский недостаточно хорошо знаю, либо этот метод просто делает слишком много, поэтому он вовсе существовать не должен.
В итоге в вашем адаптере остаётся только реализовать метод создания холдера onCreateViewHolder и реализовать метод холдера bindItem. По-моему, получилось избавиться от достаточно большого boilerplate куска.
class MyAdapter : BaseSelectingListAdapter<User, MyHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
MyHolder(...)//create your holder view
}
class MyHolder(itemView: View) : BaseSelectingListHolder<User>(itemView) {
override fun bindItem(item: User, isSelected: Boolean, onClick: (() -> Unit)?) {
//bind your data
}
}
Перспективы
Эта часть уже скорее для того, чтобы самому не забыть о возможных дополнениях. Если моё решение будет кто-нибудь использовать, то вот список того, что следовало бы дополнить:
- Потокобезопасность.
- Изменение типа выбора элементов (с одиночного на множественный, например), то есть подмена SelectionManager'а.
Сылки
Исходные коды для библиотек:
Ссылки в Gradle:
implementation 'ru.ircover.selectionmanager:core:1.1.0'
implementation 'ru.ircover.selectionmanager:livesource:1.0.1'
implementation 'ru.ircover.selectionmanager:selectingadapter:1.0.0'
===========
Источник:
habr.com
===========
Похожие новости:
- Сертификаты Let's Encrypt перестанут восприниматься на 33% Android-устройств
- [Разработка мобильных приложений, Разработка под Android, Тестирование мобильных приложений, Облачные сервисы] Отладка приложений в экосистеме Huawei: облачная платформа для дебаггинга, сервисы A/B- и открытого тестирования
- [Разработка веб-сайтов, Разработка под iOS, Разработка мобильных приложений, Разработка под Android] Хабр ПРО. Mobile-разработка: своя команда vs аутсорс
- [Разработка под Android, Профессиональная литература] Книга «Android. Программирование для профессионалов. 4-е издание»
- [Разработка мобильных приложений] Как Kotlin Multiplatform экономит время на разработку. Личный опыт создания игрового приложения для KotlinConf 2019
- [Разработка под Android, Kotlin] Solving coding problems with Kotlin: Collection functions
- [Программирование, Kotlin, Управление продуктом] Kotlin: язык программирования как продукт
- [Разработка мобильных приложений, Разработка игр, Игры и игровые приставки] Как я создал мобильную игру для своего ребёнка
- [Информационная безопасность, IT-инфраструктура] Зачем нужны сертифицированные средства защиты информации?
- [Разработка под Android] Kotlin Android Extensions deprecated. Что делать? Инструкция по миграции
Теги для поиска: #_razrabotka_pod_android (Разработка под Android), #_kotlin, #_android, #_mvvm, #_kotlin, #_adapter, #_razrabotka_pod_android (
Разработка под Android
), #_kotlin
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:48
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Итак, я наконец-то добрался до кульминации своей идеи с библиотекой, включающей в себя логику выбора элементов из списка в адаптере. После решения, независимого от платформы, и библиотеки, основанной на LiveData, я написал то, что поможет быстро и легко связать всё это с адаптером, чтобы сократить код в целом. Интерфейс SelectingListAdapter Начнём с простого интерфейса SelectingListAdapter, который я добавил для адаптера с обычным линейным списком. По моему опыту, где-то 90-95% адаптеров реализуются именно в таком виде. interface SelectingListAdapter<T> {
fun setListItems(items: ArrayList<T>) } Ничего сложного — только обновление списка элементов. И, на мой взгляд, не составит труда добавить этот интерфейс к списку реализуемых в вашем адаптере. Зачем? Чтобы можно было использовать оба метода расширения, которые я укажу далее: fun <T> SelectingListAdapter<T>.observeItemsChange(lifecycleOwner: LifecycleOwner,
liveDataSource: LiveDataSource<T>) { liveDataSource.allItems.observe(lifecycleOwner, { items -> setListItems(items) }) } Метод observeItemsChange подписывает на изменения списка элементов в LiveDataSource. fun RecyclerView.Adapter<*>.observeSelectionChange(lifecycleOwner: LifecycleOwner,
liveDataSource: LiveDataSource<*>) { liveDataSource.observeSelectionChange(lifecycleOwner) { position, _ -> notifyItemChanged(position) } } Метод observeSelectionChange уже подписывает на изменения в выборе элементов. Здесь происходит только оповещение о том, что элемент надо обновить. О проверке, выбран ли уже этот элемент, будет описано ниже. fun <T, TAdapter> TAdapter.observeAllChanges(lifecycleOwner: LifecycleOwner,
liveDataSource: LiveDataSource<T>) where TAdapter : RecyclerView.Adapter<*>, TAdapter : SelectingListAdapter<T> { observeSelectionChange(lifecycleOwner, liveDataSource) observeItemsChange(lifecycleOwner, liveDataSource) } Ну и если хочется вызвать сразу оба метода, есть observeAllChanges, который сделает это одной строкой. Класс BaseSelectingListAdapter А вот для реализации базового поведения, в котором уже адаптер обращается к LiveDataSource, я написал абстрактный класс адаптера. Обращаю так же внимание, что под этот адаптер так же используется специальный абстрактный класс холдера: abstract class BaseSelectingListAdapter<T, VH: BaseSelectingListHolder<T>>
: RecyclerView.Adapter<VH>(), SelectingListAdapter<T> { var callback: SelectingListAdapterCallback? = null ... } abstract class BaseSelectingListHolder<T>(itemView: View) : RecyclerView.ViewHolder(itemView) { abstract fun bindItem(item: T, isSelected: Boolean, onClick: (() -> Unit)?) } Пока сосредоточимся на том, что же это за SelectingListAdapterCallback. interface SelectingListAdapterCallback {
fun isItemSelected(position: Int): Boolean fun clickItem(position: Int) } Это весьма простенький колбэк, на котором будет как раз определение, выбран ли запрашиваемый элемент, а так же на него будет перенаправлена обработка клика по элементу. И в BaseSelectingListAdapter есть метод, чтобы создать этот самый колбэк на основании LiveDataSource. fun <T> setCallback(liveDataSource: LiveDataSource<T>) {
callback = object : SelectingListAdapterCallback { override fun isItemSelected(position: Int) = liveDataSource.isItemSelected(position) override fun clickItem(position: Int) { liveDataSource.clickPosition(position) } } } В остальном класс BaseSelectingListAdapter представляет из себя обычную реализацию адаптера с линейным списком — настолько скучно, что даже не хочу этот код приводить здесь. fun fullyInitialize(lifecycleOwner: LifecycleOwner,
liveDataSource: LiveDataSource<T>) { observeAllChanges(lifecycleOwner, liveDataSource) setCallback(liveDataSource) } Ну и метод fullyInitialize, который помимо установки колбэка ещё и сразу подписывает на все изменения. Лучше названия для него придумать так и не смог. Либо английский недостаточно хорошо знаю, либо этот метод просто делает слишком много, поэтому он вовсе существовать не должен. В итоге в вашем адаптере остаётся только реализовать метод создания холдера onCreateViewHolder и реализовать метод холдера bindItem. По-моему, получилось избавиться от достаточно большого boilerplate куска. class MyAdapter : BaseSelectingListAdapter<User, MyHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = MyHolder(...)//create your holder view } class MyHolder(itemView: View) : BaseSelectingListHolder<User>(itemView) { override fun bindItem(item: User, isSelected: Boolean, onClick: (() -> Unit)?) { //bind your data } } Перспективы Эта часть уже скорее для того, чтобы самому не забыть о возможных дополнениях. Если моё решение будет кто-нибудь использовать, то вот список того, что следовало бы дополнить:
Сылки Исходные коды для библиотек: Ссылки в Gradle: implementation 'ru.ircover.selectionmanager:core:1.1.0' implementation 'ru.ircover.selectionmanager:livesource:1.0.1' implementation 'ru.ircover.selectionmanager:selectingadapter:1.0.0' =========== Источник: habr.com =========== Похожие новости:
Разработка под Android ), #_kotlin |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:48
Часовой пояс: UTC + 5