Динамическая генерация UI в Android приложении при помощи RecyclerView и AdapterDelegates
Привет!))
Постараюсь рассказать об опыте использования динамической генерации UI в Android приложении при помощи RecyclerView и библиотеки https://github.com/sockeqwe/AdapterDelegates
Есть разные способы работы с UI в Android приложении- например, вы можете сделать UI статическим, либо сгенерировать при помощи Jetpack Compose, а можно выбрать и некий промежуточный вариант, где элементы будут переиспользуемы на уровне кода, а для их отображения будет использоваться RecyclerView. Сначала добавляем библиотеку по соответствующей инструкции с ее страницы.
В библиотеке есть класс AdapterDelegat, в котором есть метод isForViewType, он возвращает, относится ли элемент в списке к указанному классу. Сначала создаем интерфейс, который будет относиться к определенной странице приложения и наследовать класс DiffEquals
interface DiffEquals {
fun isItemSame(other: Any?): Boolean
fun isContentSame(other: Any?): Boolean
}
interface AccountAdapterMarker : DiffEquals
// Если элемент необходимо переиспользовать,
// создаем super интерфейс, который будет наследовать
// все интерфейсы страниц, на которых должен быть этот элемент,
// а в делегате в качестве дженерика используем этот super интерфейс,
// а не интерфейс страницы приложения
и для каждого уникального элемента в списке создаем класс, где будут храниться его данные, и будет некая логика для сравнения, аналогичная DiffUtil.ItemCallback
data class AccountName(
val accountName: String,
) : AccountAdapterMarker {
override fun isItemSame(other: Any?): Boolean {
return // логика сравнения
}
override fun isContentSame(other: Any?): Boolean {
return // логика сравнения
}
далее создаем делегат для этого класса, наследующийся от класса библиотеки AdapterDelegate с дженериком интерфейса
class AccountNameDelegate @Inject constructor() :
AdapterDelegate<MutableList<AccountAdapterMarker>>() {
// Если элемент необходимо переиспользовать,
// создаем super интерфейс, который будет наследовать
// все интерфейсы страниц, на которых должен быть этот элемент,
// а в делегате в качестве дженерика используем этот super интерфейс,
// а не интерфейс страницы приложения
var accontNameClickListener: ((accountName: AccountName) -> Unit)? = null
override fun isForViewType(items: MutableList<CommonTitleAdapterMarker>, position: Int): Boolean {
return items[position] is AccountName
}
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder {
return ViewHolder(parent.inflateBinding(ListItemAccountNameBinding::inflate))
}
override fun onBindViewHolder(
items: MutableList<AccountAdapterMarker>,
position: Int,
holder: RecyclerView.ViewHolder,
payloads: MutableList<Any>
) {
(holder as ViewHolder).bind(items[position] as AccountName)
}
private inner class ViewHolder(
private val binding: ListItemAccountNameBinding,
) : RecyclerView.ViewHolder(binding.root) {
fun bind(accountName: AccountName) {
binding.tvAccountName.text = accountName.accountName
binding.tvAccountName.setOnClickListener{
accontNameClickListener?.invoke(accountName) }
}
}
}
соответственно, делаем такой делегат для каждого элемента, который должен присутствовать на странице. Далее, делаем адаптер, в который добавляем делегаты этих элементов, также с дженериком интерфейса страницы приложения
class AccountAdapter @Inject constructor(
private val accountNameDelegate: AccountNameDelegate,
) : AsyncListDifferDelegationAdapter<AccountAdapterMarker>(DefaultDiffUtilCallback()) {
var accountClickListener: AccountClickListener? = null
set(value) {
field = value
accountNameDelegate.accontNameClickListener = { field?.onAccountNameClicked(it) }
}
init {
items = mutableListOf()
@Suppress("UNCHECKED_CAST")
delegatesManager.apply {
addDelegate(
// каст нужен только если исопльзуем super интерфейс
// для переиспользования элементов, а этом примере
// он не обязателен
accountNameDelegate as AdapterDelegate<MutableList<AccountAdapterMarker>>
)
}
}
// наследуем этот интерфейс во фрагменте,
// чтобы обрабатывать нажатия и другие действия
// и прописываем во фрагменте
// accountAdapter.accountClickListener = this
interface AccountClickListener{
fun onAccountNameClicked(accountName: AccountName)
}
}
ну а далее нужно во ViewModel или специальном маппере создать список с экземплярами классов, наследующими AccountAdapterMarker, и вызвать у адаптера метод setItems, а адаптера уже разребется, инстансы какого класса в нем находятся, и добавит эти элементы в RecyclerView.
Надеюсь, было понятно, если найдете ошибку или есть вопросы- пишите на
Telegram @RomanKryvolapov или
roman.kryvolapov@gmail.com или
https://www.linkedin.com/in/roman-kryvolapov/