RecyclerView — компонент Android Jetpack для эффективного отображения больших наборов данных в виде списков и сеток. В отличие от ListView, RecyclerView переиспользует ViewHolder'ы и управляет разметкой через LayoutManager. По данным Google (Android Developers, 2026), RecyclerView обрабатывает до 60 кадров в секунду при прокрутке списка из 1000+ элементов благодаря пулингу ViewHolder'ов и дифф-алгоритму DiffUtil. Подробнее об архитектуре Android-приложений читайте в нашей статье о ListView.
Главное
RecyclerView — это компонент библиотеки Android Jetpack, представленный на Google I/O 2014 как часть Android Support Library v7. Он предназначен для отображения динамических списков и сеток с высокой производительностью. RecyclerView появился как замена ListView и решает ключевую проблему предшественника — отсутствие принудительного переиспользования ViewHolder'ов.
Архитектура RecyclerView построена на паттерне Model-View-Adapter: данные хранятся в модели (List, LiveData, Flow), отображение управляется Adapter'ом, а компоновку контролирует LayoutManager. Каждый компонент отвечает за свою зону ответственности, что делает систему гибкой и расширяемой.
По данным опроса Android Developers (2025), RecyclerView используется в 78% Android-приложений из топ-100 Google Play. Производительность прокрутки списка из 1000+ элементов остаётся стабильной на 60 FPS на устройствах среднего сегмента благодаря трём механизмам: пулинг ViewHolder'ов, дифф-расчёт обновлений и асинхронная загрузка данных.
RecyclerView оптимален для: списков с неизвестным количеством элементов (ленты, чаты, логи), сеток из N колонок (галереи, каталоги), каскадных макетов (доски Pinterest), списков с анимацией добавления/удаления элементов. Для простых экранов с 2–5 элементами LinearLayout или ScrollView проще и эффективнее — RecyclerView добавляет накладные расходы.
Для использования RecyclerView добавьте зависимость в build.gradle (app): implementation 'androidx.recyclerview:recyclerview:1.4.0'. Минимальная версия API — 14 (Android 4.0). Начиная с AndroidX RecyclerView 1.3.0, доступны модули recyclerview-selection для выделения элементов и recyclerview-swipe для свайпов.
RecyclerView разделён на три независимых слоя: Adapter управляет данными и ViewHolder'ами, LayoutManager отвечает за позиционирование элементов, ItemAnimator контролирует анимации. Такая архитектура позволяет заменять любой слой без изменения остальных — например, переключиться с LinearLayoutManager на GridLayoutManager, не меняя код Adapter'а.
| Компонент | Обязанности | Реализация |
|---|---|---|
| Adapter | Создание ViewHolder'ов, привязка данных к View, уведомление об изменениях | RecyclerView.Adapter<VH> |
| ViewHolder | Кэширование ссылок на View, избегание findViewById() | RecyclerView.ViewHolder |
| LayoutManager | Размещение элементов, определение направления скролла | LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager |
| ItemAnimator | Анимация добавления, удаления, перемещения элементов | DefaultItemAnimator |
| ItemDecoration | Отрисовка разделителей, отступов, фонов между элементами | RecyclerView.ItemDecoration |
Цикл работы: (1) Adapter создаёт ViewHolder через onCreateViewHolder(), (2) LayoutManager определяет позицию нового View на экране, (3) Adapter привязывает данные через onBindViewHolder(), (4) при прокрутке ViewHolder'ы, ушедшие за экран, попадают в пул (RecycledViewPool) для переиспользования. Вместо создания новых View переиспользуются существующие — это ключевой механизм производительности.
ViewHolder — это класс, который хранит ссылки на View (TextView, ImageView и т. д.) внутри элемента списка. Без ViewHolder при каждом скролле система вызывала бы findViewById() — одну из самых дорогих операций Android UI. ViewHolder устраняет этот вызов, запоминая ссылки после первого связывания.
// Модель данных
data class Article(
title: String,
summary: String,
author: String,
imageUrl: String?
)
// ViewHolder — хранит ссылки на View
class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val titleText = itemView.findViewById<TextView>(R.id.tvTitle)
private val summaryText = itemView.findViewById<TextView>(R.id.tvSummary)
private val authorText = itemView.findViewById<TextView>(R.id.tvAuthor)
fun bind(article: Article) {
titleText.text = article.title
summaryText.text = article.summary
authorText.text = article.author
}
}
Практическое правило: каждый View в макете item-разметки должен быть объявлен как свойство ViewHolder с findViewById() ровно один раз — в конструкторе. Метод bind() не вызывает findViewById(), а только устанавливает текст, цвета и слушатели. Такой подход ускоряет прокрутку списка на 30–50% по данным Google (Android Performance Patterns, 2025).
LayoutManager определяет, как элементы располагаются внутри RecyclerView. По умолчанию Android предоставляет три реализации: LinearLayoutManager, GridLayoutManager и StaggeredGridLayoutManager. Выбор LayoutManager влияет на восприятие данных пользователем и производительность прокрутки.
| LayoutManager | Расположение | Когда использовать | Метод установки |
|---|---|---|---|
| LinearLayoutManager | Список (вертикальный или горизонтальный) | Ленты, чаты, уведомления, списки товаров | LinearLayoutManager(context) |
| GridLayoutManager | Сетка из N колонок | Галереи, каталоги, иконки, сетка изображений | GridLayoutManager(context, spanCount) |
| StaggeredGridLayoutManager | Каскадная сетка (разная высота элементов) | Доски Pinterest, заметки, masonry-галереи | StaggeredGridLayoutManager(spanCount, orientation) |
// Установка LinearLayoutManager в Fragment
class ArticleListFragment : Fragment() {
private lateinit var binding: FragmentArticleListBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val recyclerView = binding.recyclerView
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = ArticleAdapter(articlesList)
// Оптимизация: фиксированный размер + пул
recyclerView.setHasFixedSize(true)
recyclerView.setItemViewCacheSize(20)
// Разделитель между элементами
val divider = DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
recyclerView.addItemDecoration(divider)
}
}
Производительность: LinearLayoutManager с вертикальным скроллом — самый быстрый вариант, так как элементы располагаются последовательно и LayoutManager вычисляет позиции за O(1). GridLayoutManager требует дополнительных вычислений для разбивки на колонки, но остаётся производительным при фиксированном spanCount. StaggeredGridLayoutManager — самый медленный из трёх, так как ему нужно рассчитывать позицию элемента в каскаде с учётом высоты соседних элементов. Используйте его только когда визуально требуется разная высота ячеек.
Adapter в RecyclerView реализует паттерн Adapter из GoF: он преобразует данные (List<T>) в ViewHolder'ы, отображаемые на экране. При изменении данных Adapter может уведомить RecyclerView через методы notifyDataSetChanged(), notifyItemInserted() и аналогичные. Проблема в том, что notifyDataSetChanged() перерисовывает все видимые элементы, что приводит к подёргиванию (jank).
// Adapter с DiffUtil для эффективного обновления
class ArticleAdapter : RecyclerView.Adapter<ArticleViewHolder>() {
private var articles: List<Article> = emptyList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_article, parent, false)
return ArticleViewHolder(view)
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
holder.bind(articles[position])
}
override fun getItemCount(): Int = articles.size
// Обновление списка через DiffUtil
fun updateArticles(newArticles: List<Article>) {
val diffCallback = ArticleDiffCallback(articles, newArticles)
val diffResult = DiffUtil.calculateDiff(diffCallback)
articles = newArticles
diffResult.dispatchUpdatesTo(this)
}
}
// Callback для DiffUtil
class ArticleDiffCallback(
private val oldList: List<Article>,
private val newList: List<Article>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {
return oldList[oldPos].title == newList[newPos].title
}
override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {
return oldList[oldPos] == newList[newPos]
}
}
DiffUtil использует алгоритм Eugene W. Myers' difference algorithm, который вычисляет минимальное количество операций (insert, remove, move, change) для преобразования старого списка в новый. Для списка из 100 элементов DiffUtil работает за 1–5 мс (Google, 2025). Для больших списков (10000+) используйте AsyncListDiffer или PagingDataAdapter из библиотеки Paging — они выполняют расчёты в фоновом потоке.
ListView — предшественник RecyclerView, появившийся в Android 1.0 (API 1). Он также отображает списки, но не навязывает ViewHolder, не поддерживает LayoutManager и не умеет анимировать изменения. RecyclerView разработан как замена ListView с более строгой архитектурой и производительностью.
| Критерий | RecyclerView | ListView |
|---|---|---|
| ViewHolder | Обязателен (архитектура) | Рекомендуется, но не обязателен |
| LayoutManager | Разделение: список, сетка, каскад | Только вертикальный список |
| Анимация изменений | DefaultItemAnimator из коробки | Нет встроенной поддержки |
| Уведомление об изменениях | Точечные: notifyItemInserted/Removed | notifyDataSetChanged() только весь список |
| DiffUtil | Поддержка через DiffUtil.Callback | Нет эквивалента |
| Разделители | ItemDecoration (кастомные) | android:divider (встроенный) |
| Минимальная версия | API 14 (Android 4.0) через AndroidX | API 1 (все версии) |
Рекомендация Google (Android Developers, 2026): используйте RecyclerView для всех новых проектов. ListView можно оставить в старых кодовых базах (legacy), где миграция не оправдывает затрат. RecyclerView обеспечивает лучшую производительность на 100+ элементах, поддерживает горизонтальный скролл и сетки без дополнительных костылей. В Android 15 ListView официально помечен как частично устаревший.
Часто задаваемые вопросы
Проверьте три условия: (1) Adapter установлен через recyclerView.adapter = adapter, (2) LayoutManager установлен через recyclerView.layoutManager = LinearLayoutManager(context), (3) данные в списке не пустые и adapter уведомлён (notifyDataSetChanged()). Типичная ошибка — установка данных после вызова notifyDataSetChanged(). Данные должны быть присвоены до уведомления. Проверьте размер списка: adapter.itemCount должен быть больше 0.
Тормоза при прокрутке (jank) возникают, если onBindViewHolder() выполняет тяжёлые операции: загрузку изображений без кэширования, findViewById() внутри bind, сложные вычисления. Решения: (1) используйте библиотеку Glide или Coil для асинхронной загрузки изображений, (2) вынесите все findViewById() в конструктор ViewHolder, (3) для дифф-расчётов используйте AsyncListDiffer. Включите профилирование GPU в Android Studio (Profile GPU Rendering) для поиска медленных фреймов.
Передайте ориентацию в LayoutManager: LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false). Второй параметр — reverseLayout (false = слева направо). Для горизонтальной сетки используйте GridLayoutManager с горизонтальной ориентацией или кастомный LinearSnapHelper для постраничной прокрутки. Горизонтальные списки хорошо подходят для каруселей товаров, изображений, категорий.
RecyclerView не имеет встроенного onClickListener (в отличие от ListView с onItemClickListener). Реализуйте его в ViewHolder: передайте лямбду в конструктор и установите itemView.setOnClickListener. Пример: class ArticleViewHolder(itemView: View, val onItemClick: (Article) -> Unit) : RecyclerView.ViewHolder(itemView). Вызывайте onItemClick(article) внутри bind(). Альтернатива — установить RecyclerView.addOnItemTouchListener() для обработки жестов.
RecycledViewPool — внутренний пул ViewHolder'ов, которые стали невидимыми при скролле. Вместо уничтожения они сохраняются в пуле и переиспользуются. По умолчанию пул хранит 5 ViewHolder'ов каждого типа. Увеличьте размер для сложных макетов: recyclerView.getRecycledViewPool().setMaxRecycledViews(VIEW_TYPE_NORMAL, maxCount). Если в RecyclerView несколько типов View (viewType), пул хранит отдельные стеки для каждого. Совместное использование RecycledViewPool между разными RecyclerView экономит память в экранах с вложенными списками.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также