← Назад к вопросам

Как реализовать собственный адаптер для RecyclerView?

2.0 Middle🔥 181 комментариев
#UI и вёрстка

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Реализация собственного адаптера для RecyclerView

Создание собственного адаптера для RecyclerView — фундаментальный навык Android-разработчика. Это позволяет полностью контролировать отображение данных, оптимизировать производительность и реализовывать сложные UI.

Базовая структура адаптера

Кастомный адаптер наследуется от RecyclerView.Adapter и требует определения ViewHolder. Вот минимальная реализация:

class CustomAdapter(
    private val items: List<DataItem>,
    private val onItemClick: (DataItem) -> Unit
) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

    // 1. Определяем ViewHolder
    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val titleTextView: TextView = itemView.findViewById(R.id.tvTitle)
        private val subtitleTextView: TextView = itemView.findViewById(R.id.tvSubtitle)

        fun bind(item: DataItem) {
            titleTextView.text = item.title
            subtitleTextView.text = item.subtitle
            
            itemView.setOnClickListener {
                onItemClick(item)
            }
        }
    }

    // 2. Создаем новые ViewHolder'ы
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_custom_layout, parent, false)
        return ViewHolder(view)
    }

    // 3. Привязываем данные к ViewHolder'у
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(items[position])
    }

    // основания. Возвращаем количество элементов
    override fun getItemCount(): Int = items.size
}

Ключевые компоненты реализации

1. ViewHolder Pattern

ViewHolder кэширует ссылки на View элементы, предотвращая повторные findViewById():

  • Объявляется как inner class адаптера
  • Хранит ссылки на все View, требующие обновления
  • Содержит метод bind() для заполнения данными

2. Методы жизненного цикла адаптера

  • onCreateViewHolder(): создает новый ViewHolder и inflate'ит макет
  • onBindViewHolder(): связывает данные с ViewHolder по позиции
  • getItemCount(): возвращает общее количество элементов

3. Обработка кликов

Правильная реализация обработки кликов критически важна:

// Способ 1: Передача listener через конструктор
class CustomAdapter(
    private val onItemClick: (DataItem) -> Unit
) : RecyclerView.Adapter<ViewHolder>() {
    
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.itemView.setOnClickListener {
            onItemClick(items[position])
        }
    }
}

// Способ 2: Интерфейс для сложных взаимодействий
interface ItemClickListener {
    fun onItemClick(item: DataItem)
    fun onItemLongClick(item: DataItem): Boolean
}

Расширенные возможности

Поддержка разных типов элементов

Для отображения разных макетов в одном списке:

override fun getItemViewType(position: Int): Int {
    return when(items[position]) {
        is DataItem.Header -> VIEW_TYPE_HEADER
        is DataItem.Content -> VIEW_TYPE_CONTENT
        is DataItem.Footer -> VIEW_TYPE_FOOTER
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    return when(viewType) {
        VIEW_TYPE_HEADER -> HeaderViewHolder(inflate(R.layout.item_header, parent))
        VIEW_TYPE_CONTENT -> ContentViewHolder(inflate(R.layout.item_content, parent))
        else -> FooterViewHolder(inflate(R.layout.item_footer, parent))
    }
}

Дифференциальные обновления с DiffUtil

Для оптимального обновления данных используйте DiffUtil:

class CustomAdapter : RecyclerView.Adapter<ViewHolder>() {
    private val diffCallback = object : DiffUtil.ItemCallback<DataItem>() {
        override fun areItemsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
            return oldItem.id == newItem.id
        }
        
        override fun areContentsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
            return oldItem == newItem
        }
    }
    
    private val differ = AsyncListDiffer(this, diffCallback)
    
    fun submitList(list: List<DataItem>) {
        differ.submitList(list)
    }
}

Привязка данных с ViewBinding

Современный подход с ViewBinding:

class ViewHolder(private val binding: ItemLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
    fun bind(item: DataItem) {
        binding.tvTitle.text = item.title
        binding.tvSubtitle.text = item.subtitle
        binding.executePendingBindings() // Оптимизация для Data Binding
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val binding = ItemLayoutBinding.inflate(
        LayoutInflater.from(parent.context),
        parent,
        false
    )
    return ViewHolder(binding)
}

Лучшие практики и оптимизации

  1. Не создавайте listener'ы в onBindViewHolder для анонимных объектов — это ведет к утечкам памяти
  2. Используйте payloads для частичных обновлений:
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: List<Any>) {
    if (payloads.isEmpty()) {
        super.onBindViewHolder(holder, position, payloads)
    } else {
        // Обновляем только измененные части
    }
}
  1. Реализуйте стабильные ID для анимаций:
override fun getItemId(position: Int): Long {
    return items[position].id.hashCode().toLong()
}

override fun setHasStableIds(hasStableIds: Boolean) {
    super.setHasStableIds(true)
}
  1. Обрабатывайте конфигурации через onAttachedToRecyclerView() и onDetachedFromRecyclerView()

Полный пример с современными подходами

class ModernAdapter(
    private val onClick: (DataItem) -> Unit
) : RecyclerView.Adapter<ModernAdapter.ViewHolder>() {

    private val items = mutableListOf<DataItem>()
    private val diffUtil = AsyncListDiffer(this, DataItemDiffCallback())

    class ViewHolder(
        private val binding: ItemLayoutBinding,
        private val onClick: (DataItem) -> Unit
    ) : RecyclerView.ViewHolder(binding.root) {
        
        private lateinit var currentItem: DataItem
        
        init {
            binding.root.setOnClickListener { onClick(currentItem) }
        }
        
        fun bind(item: DataItem) {
            currentItem = item
            binding.apply {
                tvTitle.text = item.title
                tvSubtitle.text = item.subtitle
                ivIcon.setImageResource(item.iconRes)
                // Используем Glide/Coil для изображений
                Coil.load(item.imageUrl).into(ivPhoto)
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemLayoutBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return ViewHolder(binding, onClick)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(diffUtil.currentList[position])
    }

    override fun getItemCount(): Int = diffUtil.currentList.size

    fun updateItems(newItems: List<DataItem>) {
        diffUtil.submitList(newItems)
    }
}

Собственный адаптер предоставляет полный контроль над отображением списков. Ключевые аспекты успешной реализации: правильное использование ViewHolder, оптимизация через DiffUtil, корректная обработка кликов и конфигураций, а также следование современным практикам с ViewBinding и корутинами для асинхронных операций.