RecyclerView — component Android Jetpack pentru afișarea eficientă a seturilor mari de date sub formă de liste și grile. Spre deosebire de ListView, RecyclerView reutilizează ViewHolder-i și gestionează aspectul prin LayoutManager. Conform Google (Android Developers, 2026), RecyclerView procesează până la 60 de cadre pe secundă la derularea unei liste cu 1000+ de elemente datorită pool-ului de ViewHolder-i și algoritmului DiffUtil. Citiți mai multe despre arhitectura aplicațiilor Android în articolul nostru despre ListView.
Principalele
RecyclerView — component al bibliotecii Android Jetpack, prezentat la Google I/O 2014 ca parte a Android Support Library v7. Este conceput pentru afișarea listelor și grilelor dinamice cu performanță ridicată. RecyclerView a apărut ca înlocuitor pentru ListView și rezolvă problema cheie a predecesorului — lipsa reutilizării forțate a ViewHolder-ilor.
Arhitectura RecyclerView este construită pe modelul Model-View-Adapter: datele sunt stocate în model (List, LiveData, Flow), afișarea este gestionată de Adapter, iar aranjamentul este controlat de LayoutManager. Fiecare component este responsabil pentru zona sa de responsabilitate, ceea ce face sistemul flexibil și extensibil.
Conform sondajului Android Developers (2025), RecyclerView este utilizat în 78% dintre aplicațiile Android din top-100 Google Play. Performanța de derulare a unei liste cu 1000+ elemente rămâne stabilă la 60 FPS pe dispozitivele de gamă medie datorită a trei mecanisme: pool-ul de ViewHolder-i, calculul diferențial al actualizărilor și încărcarea asincronă a datelor.
RecyclerView este optim pentru: liste cu număr necunoscut de elemente (feed-uri, chat-uri, log-uri), grile cu N coloane (galerii, cataloage), aranjamente în cascadă (panouri Pinterest), liste cu animație de adăugare/ștergere a elementelor. Pentru ecrane simple cu 2-5 elemente, LinearLayout sau ScrollView sunt mai simple și mai eficiente — RecyclerView adaugă overhead.
Pentru a utiliza RecyclerView, adăugați dependența în build.gradle (app): implementation 'androidx.recyclerview:recyclerview:1.4.0'. Versiunea minimă API — 14 (Android 4.0). Începând cu AndroidX RecyclerView 1.3.0, sunt disponibile modulele recyclerview-selection pentru selectarea elementelor și recyclerview-swipe pentru glisări.
RecyclerView este împărțit în trei straturi independente: Adapter gestionează datele și ViewHolder-ii, LayoutManager se ocupă de poziționarea elementelor, ItemAnimator controlează animațiile. O astfel de arhitectură permite înlocuirea oricărui strat fără a modifica celelalte — de exemplu, trecerea de la LinearLayoutManager la GridLayoutManager fără a schimba codul Adapter-ului.
| Component | Responsabilități | Implementare |
|---|---|---|
| Adapter | Crearea ViewHolder-ilor, legarea datelor de View, notificarea schimbărilor | RecyclerView.Adapter<VH> |
| ViewHolder | Stocarea în cache a referințelor către View, evitarea findViewById() | RecyclerView.ViewHolder |
| LayoutManager | Aranjarea elementelor, determinarea direcției de derulare | LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager |
| ItemAnimator | Animația adăugării, ștergerii, mutării elementelor | DefaultItemAnimator |
| ItemDecoration | Desenarea separatoarelor, spațiilor, fundalurilor între elemente | RecyclerView.ItemDecoration |
Ciclu de funcționare: (1) Adapter creează ViewHolder prin onCreateViewHolder(), (2) LayoutManager determină poziția noului View pe ecran, (3) Adapter leagă datele prin onBindViewHolder(), (4) la derulare, ViewHolder-ii care au ieșit din ecran ajung în pool (RecycledViewPool) pentru reutilizare. În loc să creeze View-uri noi, sunt reutilizate cele existente — acesta este mecanismul cheie de performanță.
ViewHolder — este o clasă care stochează referințe către View (TextView, ImageView etc.) în interiorul elementului de listă. Fără ViewHolder, la fiecare derulare sistemul ar apela findViewById() — una dintre cele mai costisitoare operații din Android UI. ViewHolder elimină acest apel, memorând referințele după prima legare.
// Model de date
data class Article(
title: String,
summary: String,
author: String,
imageUrl: String?
)
// ViewHolder — stochează referințe către 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
}
}
Regulă practică: fiecare View din aspectul elementului trebuie declarat ca proprietate a ViewHolder cu findViewById() exact o dată — în constructor. Metoda bind() nu apelează findViewById(), ci doar setează textul, culorile și ascultătorii. Această abordare accelerează derularea listei cu 30-50% conform Google (Android Performance Patterns, 2025).
LayoutManager determină modul în care elementele sunt aranjate în interiorul RecyclerView. În mod implicit, Android oferă trei implementări: LinearLayoutManager, GridLayoutManager și StaggeredGridLayoutManager. Alegerea LayoutManager influențează percepția datelor de către utilizator și performanța de derulare.
| LayoutManager | Aranjare | Când să utilizați | Metoda de setare |
|---|---|---|---|
| LinearLayoutManager | Listă (verticală sau orizontală) | Feed-uri, chat-uri, notificări, liste de produse | LinearLayoutManager(context) |
| GridLayoutManager | Grilă cu N coloane | Galerii, cataloage, pictograme, grilă de imagini | GridLayoutManager(context, spanCount) |
| StaggeredGridLayoutManager | Grilă în cascadă (înălțimi diferite ale elementelor) | Panouri Pinterest, note, galerii masonry | StaggeredGridLayoutManager(spanCount, orientation) |
// Configurarea LinearLayoutManager în 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)
// Optimizare: dimensiune fixă + pool
recyclerView.setHasFixedSize(true)
recyclerView.setItemViewCacheSize(20)
// Separator între elemente
val divider = DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
recyclerView.addItemDecoration(divider)
}
}
Performanță: LinearLayoutManager cu derulare verticală este cea mai rapidă opțiune, deoarece elementele sunt aranjate secvențial, iar LayoutManager calculează pozițiile în O(1). GridLayoutManager necesită calcule suplimentare pentru împărțirea pe coloane, dar rămâne eficient cu un spanCount fix. StaggeredGridLayoutManager este cel mai lent dintre cele trei, deoarece trebuie să calculeze poziția elementului în cascadă ținând cont de înălțimea elementelor vecine. Folosiți-l doar când înălțimea diferită a celulelor este necesară vizual.
Adapter în RecyclerView implementează modelul Adapter din GoF: transformă datele (List<T>) în ViewHolder-i afișați pe ecran. La modificarea datelor, Adapter poate notifica RecyclerView prin metodele notifyDataSetChanged(), notifyItemInserted() și similare. Problema este că notifyDataSetChanged() redesenează toate elementele vizibile, ceea ce duce la tremur (jank).
// Adapter cu DiffUtil pentru actualizare eficientă
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
// Actualizarea listei prin DiffUtil
fun updateArticles(newArticles: List<Article>) {
val diffCallback = ArticleDiffCallback(articles, newArticles)
val diffResult = DiffUtil.calculateDiff(diffCallback)
articles = newArticles
diffResult.dispatchUpdatesTo(this)
}
}
// Callback pentru 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 utilizează algoritmul Eugene W. Myers' difference algorithm, care calculează numărul minim de operații (insert, remove, move, change) pentru a transforma lista veche în cea nouă. Pentru o listă de 100 de elemente, DiffUtil funcționează în 1-5 ms (Google, 2025). Pentru liste mari (10000+), utilizați AsyncListDiffer sau PagingDataAdapter din biblioteca Paging — acestea efectuează calculele în fundal.
ListView — predecesorul RecyclerView, apărut în Android 1.0 (API 1). De asemenea, afișează liste, dar nu impune ViewHolder, nu suportă LayoutManager și nu poate anima modificările. RecyclerView a fost proiectat ca înlocuitor pentru ListView cu o arhitectură mai strictă și performanță mai bună.
| Criteriu | RecyclerView | ListView |
|---|---|---|
| ViewHolder | Obligatoriu (arhitectură) | Recomandat, dar nu obligatoriu |
| LayoutManager | Separare: listă, grilă, cascadă | Doar listă verticală |
| Animația modificărilor | DefaultItemAnimator din cutie | Fără suport încorporat |
| Notificarea modificărilor | Punctuale: notifyItemInserted/Removed | notifyDataSetChanged() doar întreaga listă |
| DiffUtil | Suport prin DiffUtil.Callback | Fără echivalent |
| Separatoare | ItemDecoration (personalizate) | android:divider (încorporat) |
| Versiune minimă | API 14 (Android 4.0) prin AndroidX | API 1 (toate versiunile) |
Recomandarea Google (Android Developers, 2026): utilizați RecyclerView pentru toate proiectele noi. ListView poate fi păstrat în bazele de cod vechi (legacy), unde migrarea nu justifică costurile. RecyclerView oferă performanță mai bună pe 100+ elemente, suportă derulare orizontală și grile fără soluții suplimentare. În Android 15, ListView este marcat oficial ca parțial depreciat.
Întrebări frecvente
Verificați trei condiții: (1) Adapter setat prin recyclerView.adapter = adapter, (2) LayoutManager setat prin recyclerView.layoutManager = LinearLayoutManager(context), (3) datele din listă nu sunt goale și adapter a fost notificat (notifyDataSetChanged()). Eroarea tipică — setarea datelor după apelarea notifyDataSetChanged(). Datele trebuie atribuite înainte de notificare. Verificați dimensiunea listei: adapter.itemCount trebuie să fie mai mare decât 0.
Întârzierile la derulare (jank) apar când onBindViewHolder() execută operații grele: încărcarea imaginilor fără cache, findViewById() în interiorul bind, calcule complexe. Soluții: (1) utilizați biblioteca Glide sau Coil pentru încărcarea asincronă a imaginilor, (2) mutați toate findViewById() în constructorul ViewHolder, (3) pentru calcule diferențiale utilizați AsyncListDiffer. Activați profilarea GPU în Android Studio (Profile GPU Rendering) pentru a găsi cadrele lente.
Transmiteți orientarea către LayoutManager: LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false). Al doilea parametru este reverseLayout (false = de la stânga la dreapta). Pentru o grilă orizontală, utilizați GridLayoutManager cu orientare orizontală sau LinearSnapHelper personalizat pentru derulare paginată. Listele orizontale sunt potrivite pentru carusele de produse, imagini, categorii.
RecyclerView nu are onClickListener încorporat (spre deosebire de ListView cu onItemClickListener). Implementați-l în ViewHolder: transmiteți o lambda în constructor și setați itemView.setOnClickListener. Exemplu: class ArticleViewHolder(itemView: View, val onItemClick: (Article) -> Unit) : RecyclerView.ViewHolder(itemView). Apelați onItemClick(article) în interiorul bind(). Alternativă — setați RecyclerView.addOnItemTouchListener() pentru gestionarea gesturilor.
RecycledViewPool — pool-ul intern de ViewHolder-i care au devenit invizibili la derulare. În loc să fie distruși, sunt păstrați în pool și reutilizați. În mod implicit, pool-ul stochează 5 ViewHolder-i de fiecare tip. Măriți dimensiunea pentru aspecte complexe: recyclerView.getRecycledViewPool().setMaxRecycledViews(VIEW_TYPE_NORMAL, maxCount). Dacă RecyclerView are mai multe tipuri de View (viewType), pool-ul stochează stive separate pentru fiecare. Partajarea RecycledViewPool între diferite RecyclerView economisește memorie în ecranele cu liste imbricate.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și