---
title: "Динамично генериране на UI в Android приложение с помощта на RecyclerView и AdapterDelegates"
url: "https://romankryvolapov.com/bg/recyclerview-adapterdelegates/"
description: "Как да изградим динамичен UI с RecyclerView и библиотеката AdapterDelegates: делегати, елементи на списъка за повторно използване и сравнение чрез DiffEquals."
language: bg
published: 2023-04-24
updated: 2026-11-13
---
Ще се опитам да разкажа за опита си с използването на динамично генериране на UI в Android приложение с помощта на RecyclerView и библиотеката

[https://github.com/sockeqwe/AdapterDelegates](https://github.com/sockeqwe/AdapterDelegates)

Има различни начини за работа с UI в Android приложение – например можете да направите UI статичен или да го генерирате с помощта на Jetpack Compose, а можете да изберете и някакъв междинен вариант, при който елементите ще се използват повторно на ниво код, а за показването им ще се използва RecyclerView. Първо добавяме библиотеката по съответната инструкция от нейната страница.

## Създаване на интерфейси и класове

В библиотеката има клас AdapterDelegate, в който има метод isForViewType, той връща дали елементът в списъка принадлежи към посочения клас. Първо създаваме интерфейс, който ще се отнася до определена страница на приложението и ще наследява интерфейса DiffEquals

Ако елементът трябва да се използва повторно, създаваме super интерфейс, който ще наследява всички интерфейси на страниците, на които трябва да присъства този елемент, а в делегата като генерик използваме този super интерфейс, а не интерфейса на страницата на приложението

За всеки уникален елемент в списъка създаваме клас, в който ще се съхраняват данните му и ще има някаква логика за сравнение, аналогична на DiffUtil.ItemCallback

```kotlin
interface DiffEquals {


    fun isItemSame(other: Any?): Boolean


    fun isContentSame(other: Any?): Boolean


}
```

```kotlin
fun interface DeepCopy<T> {

    fun deepCopy(): T

}
```

```kotlin
@Suppress("UNCHECKED_CAST")
inline fun <reified T> T.equalTo(other: Any?, vararg properties: T.() -> Any?): Boolean {
    if (other !is T) return false
    properties.forEach {
        val currentValue = it.invoke(this)
        val otherValue = it.invoke(other)
        if (currentValue is List<*>) {
            if (otherValue !is List<*> || !currentValue.isListContentEqualsOrSame(otherValue)) {
                return false
            }
        } else if (it.invoke(this) != it.invoke(other)) {
            return false
        }
    }
    return true
}

/**
 * Compare two lists by their content.
 * The entity of this list must have a DiffEquals implementation.
 * @return true when same, false otherwise
 */
fun <T : DiffEquals> List<T>.isListContentSame(other: List<T>): Boolean {
    if (size != other.size) return false
    withIndex().forEach {
        if (!it.value.isContentSame(other[it.index])) {
            return false
        }
    }
    return true
}

/**
 * Compare two lists by their content.
 * This method uses a default equal operation to compare list items
 * @return true when same, false otherwise
 */
fun List<*>.isListContentEquals(other: List<*>): Boolean {
    if (size != other.size) return false
    withIndex().forEach {
        if (it.value != other[it.index]) {
            return false
        }
    }
    return true
}

/**
 * Compare two lists by their content.
 * This method uses a default equal operation to compare list items
 * or the diff equals mechanic if the list item is implements [DiffEquals]
 * @return true when same, false otherwise
 */
fun List<*>.isListContentEqualsOrSame(other: List<*>): Boolean {
    if (size != other.size) return false
    withIndex().forEach {
        if (it.value is DiffEquals && other[it.index] is DiffEquals) {
            if (!(it.value as DiffEquals).isContentSame(other[it.index])) {
                return false
            }
        } else if (it.value != other[it.index]) {
            return false
        }
    }
    return true
}

/**
 * Makes a deep copy of the list. The items in the list should implement
 * [DeepCopy] interface.
 */
fun <T : DeepCopy<T>> List<T>.deepCopy(): List<T> {
    val oldList = this
    return mutableListOf<T>().apply {
        oldList.forEach {
            add(it.deepCopy())
        }
    }
}

fun <T> List<T>.moveItemToFirstPosition(predicate: (T) -> Boolean): List<T> {
    for (element in this.withIndex()) {
        if (predicate(element.value)) {
            return this.toMutableList().apply {
                removeAt(element.index)
                add(0, element.value)
            }.toList()
        }
    }
    return this
}
```

```kotlin
interface AccountAdapterMarker : DiffEquals
```

```kotlin
data class AccountName(
  val accountName: String,
) : AccountAdapterMarker {


	override fun isItemSame(other: Any?): Boolean {
        return equalTo(
            other,
            { accountName },
        )
    }


    override fun isContentSame(other: Any?): Boolean {
        return equalTo(
            other,
            { accountName },
        )
    }


}
```

## Създаване на делегат

след това създаваме делегат за този клас, който наследява класа AdapterDelegate на библиотеката с генерик интерфейса

```kotlin
class AccountNameDelegate() : AdapterDelegate<MutableList<AccountAdapterMarker>>() {


  var accountNameClickListener: ((accountName: AccountName) -> Unit)? = null

  override fun isForViewType(items: MutableList<AccountAdapterMarker>, 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{
        accountNameClickListener?.invoke(accountName) }
    }
  }

}
```

## Създаване на адаптер

съответно правим такъв делегат за всеки елемент, който трябва да присъства на страницата. След това правим адаптер, в който добавяме делегатите на тези елементи, също с генерик интерфейса на страницата на приложението

```kotlin
class AccountAdapter (
  private val accountNameDelegate: AccountNameDelegate,
) : AsyncListDifferDelegationAdapter<AccountAdapterMarker>(DefaultDiffUtilCallback()) {


  var accountClickListener: AccountClickListener? = null
    set(value) {
      field = value
      accountNameDelegate.accountNameClickListener = { 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.

[ Copyright: Roman Kryvolapov ](https://t.me/RomanKryvolapov)
