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 в оформлението на елемента трябва да бъде деклариран като свойство на 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, който изчислява минималния брой операции (insert, remove, move, change) за преобразуване на стария списък в нов. За списък от 100 елемента DiffUtil работи за 1–5 ms (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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също