RecyclerView — компонента Android Jetpack за ефикасно приказивање великих скупова података у виду листи и мрежа. За разлику од ListView, RecyclerView поново користи ViewHolder-е и управља распоредом преко LayoutManager-а. Према Google-у (Android Developers, 2026), RecyclerView обрађује до 60 кадрова у секунди при померању листе од 1000+ елемената захваљујући pool-у 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 на уређајима средњег сегмента захваљујући три механизма: pool ViewHolder-а, диф-прорачун ажурирања и асинхроно учитавање података.
RecyclerView је оптималан за: листе са непознатим бројем елемената (feed-ови, четови, логови), мреже са N колона (галерије, каталози), каскадне распореде (Pinterest табле), листе са анимацијом додавања/уклањања елемената. За једноставне екране са 2-5 елемената LinearLayout или ScrollView су једноставнији и ефикаснији — RecyclerView додаје додатни overhead.
За коришћење 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-и који су изашли са екрана одлазе у pool (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 | Листа (вертикална или хоризонтална) | Feed-ови, четови, обавештења, листе производа | 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)
// Оптимизација: фиксна величина + pool
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 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 — интерни pool ViewHolder-а који су постали невидљиви при померању. Уместо уништавања, чувају се у pool-у и поново користе. Подразумевано, pool чува 5 ViewHolder-а сваког типа. Повећајте величину за сложене распореде: recyclerView.getRecycledViewPool().setMaxRecycledViews(VIEW_TYPE_NORMAL, maxCount). Ако RecyclerView има више типова View-а (viewType), pool чува одвојене стекове за сваки. Дељење RecycledViewPool-а између различитих RecyclerView-а штеди меморију на екранима са угнежђеним листама.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође