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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.