RecyclerView — komponent Android Jetpack do wydajnego wyświetlania dużych zbiorów danych w postaci list i siatek. W przeciwieństwie do ListView, RecyclerView ponownie wykorzystuje ViewHoldery i zarządza układem przez LayoutManager. Według Google (Android Developers, 2026), RecyclerView obsługuje do 60 klatek na sekundę podczas przewijania listy z 1000+ elementów dzięki puli ViewHolderów i algorytmowi różnicowemu DiffUtil. Więcej o architekturze aplikacji Android przeczytasz w naszym artykule o ListView.
Najważniejsze
RecyclerView — to komponent biblioteki Android Jetpack, przedstawiony na Google I/O 2014 jako część Android Support Library v7. Jest przeznaczony do wyświetlania dynamicznych list i siatek z wysoką wydajnością. RecyclerView pojawił się jako zamiennik ListView i rozwiązuje kluczowy problem poprzednika — brak wymuszonego ponownego wykorzystania ViewHolderów.
Architektura RecyclerView opiera się na wzorcu Model-View-Adapter: dane są przechowywane w modelu (List, LiveData, Flow), wyświetlaniem zarządza Adapter, a układ kontroluje LayoutManager. Każdy komponent odpowiada za swoją strefę odpowiedzialności, co czyni system elastycznym i rozszerzalnym.
Według ankiety Android Developers (2025), RecyclerView jest używany w 78% aplikacji Android z top-100 Google Play. Wydajność przewijania listy z 1000+ elementów pozostaje stabilna na 60 FPS na urządzeniach średniej klasy dzięki trzem mechanizmom: pula ViewHolderów, różnicowe obliczanie aktualizacji i asynchroniczne ładowanie danych.
RecyclerView jest optymalny do: list z nieznaną liczbą elementów (kanały, czaty, logi), siatek z N kolumn (galerie, katalogi), układów kaskadowych (tablice Pinterest), list z animacją dodawania/usuwania elementów. Dla prostych ekranów z 2–5 elementami LinearLayout lub ScrollView są prostsze i wydajniejsze — RecyclerView dodaje narzut.
Aby użyć RecyclerView, dodaj zależność w build.gradle (app): implementation 'androidx.recyclerview:recyclerview:1.4.0'. Minimalna wersja API — 14 (Android 4.0). Od AndroidX RecyclerView 1.3.0 dostępne są moduły recyclerview-selection do zaznaczania elementów i recyclerview-swipe do przesuwania.
RecyclerView jest podzielony na trzy niezależne warstwy: Adapter zarządza danymi i ViewHolderami, LayoutManager odpowiada za pozycjonowanie elementów, ItemAnimator kontroluje animacje. Taka architektura pozwala zastąpić dowolną warstwę bez zmiany pozostałych — na przykład przejść z LinearLayoutManager na GridLayoutManager bez zmiany kodu Adaptera.
| Komponent | Obowiązki | Implementacja |
|---|---|---|
| Adapter | Tworzenie ViewHolderów, wiązanie danych z View, powiadamianie o zmianach | RecyclerView.Adapter<VH> |
| ViewHolder | Buforowanie referencji do View, unikanie findViewById() | RecyclerView.ViewHolder |
| LayoutManager | Rozmieszczanie elementów, określanie kierunku przewijania | LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager |
| ItemAnimator | Animacja dodawania, usuwania, przenoszenia elementów | DefaultItemAnimator |
| ItemDecoration | Rysowanie separatorów, odstępów, tła między elementami | RecyclerView.ItemDecoration |
Cykl pracy: (1) Adapter tworzy ViewHolder przez onCreateViewHolder(), (2) LayoutManager określa pozycję nowego View na ekranie, (3) Adapter wiąże dane przez onBindViewHolder(), (4) podczas przewijania ViewHoldery, które zniknęły z ekranu, trafiają do puli (RecycledViewPool) do ponownego wykorzystania. Zamiast tworzyć nowe View, ponownie wykorzystuje się istniejące — to kluczowy mechanizm wydajności.
ViewHolder — to klasa przechowująca referencje do View (TextView, ImageView itp.) wewnątrz elementu listy. Bez ViewHolder przy każdym przewijaniu system wywoływałby findViewById() — jedną z najdroższych operacji Android UI. ViewHolder eliminuje to wywołanie, zapamiętując referencje po pierwszym powiązaniu.
// Model danych
data class Article(
title: String,
summary: String,
author: String,
imageUrl: String?
)
// ViewHolder — przechowuje referencje do 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
}
}
Praktyczna zasada: każdy View w układzie elementu powinien być zadeklarowany jako właściwość ViewHolder z findViewById() dokładnie raz — w konstruktorze. Metoda bind() nie wywołuje findViewById(), a jedynie ustawia tekst, kolory i słuchacze. Takie podejście przyspiesza przewijanie listy o 30–50% według Google (Android Performance Patterns, 2025).
LayoutManager określa, jak elementy są rozmieszczone wewnątrz RecyclerView. Domyślnie Android udostępnia trzy implementacje: LinearLayoutManager, GridLayoutManager i StaggeredGridLayoutManager. Wybór LayoutManager wpływa na odbiór danych przez użytkownika i wydajność przewijania.
| LayoutManager | Rozmieszczenie | Kiedy używać | Metoda ustawienia |
|---|---|---|---|
| LinearLayoutManager | Lista (pionowa lub pozioma) | Kanały, czaty, powiadomienia, listy produktów | LinearLayoutManager(context) |
| GridLayoutManager | Siatka z N kolumn | Galerie, katalogi, ikony, siatka obrazów | GridLayoutManager(context, spanCount) |
| StaggeredGridLayoutManager | Siatka kaskadowa (różna wysokość elementów) | Tablice Pinterest, notatki, galerie masonry | StaggeredGridLayoutManager(spanCount, orientation) |
// Ustawianie LinearLayoutManager w 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)
// Optymalizacja: stały rozmiar + pula
recyclerView.setHasFixedSize(true)
recyclerView.setItemViewCacheSize(20)
// Separator między elementami
val divider = DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
recyclerView.addItemDecoration(divider)
}
}
Wydajność: LinearLayoutManager z przewijaniem pionowym — najszybsza opcja, ponieważ elementy są rozmieszczane sekwencyjnie, a LayoutManager oblicza pozycje w O(1). GridLayoutManager wymaga dodatkowych obliczeń do podziału na kolumny, ale pozostaje wydajny przy stałym spanCount. StaggeredGridLayoutManager jest najwolniejszy z trzech, ponieważ musi obliczać pozycję elementu w kaskadzie uwzględniając wysokość sąsiednich elementów. Używaj go tylko gdy wizualnie wymagana jest różna wysokość komórek.
Adapter w RecyclerView implementuje wzorzec Adapter z GoF: przekształca dane (List<T>) w ViewHoldery wyświetlane na ekranie. Przy zmianie danych Adapter może powiadomić RecyclerView przez metody notifyDataSetChanged(), notifyItemInserted() i podobne. Problem polega na tym, że notifyDataSetChanged() przerysowuje wszystkie widoczne elementy, co prowadzi do drgań (jank).
// Adapter z DiffUtil do wydajnej aktualizacji
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
// Aktualizacja listy przez DiffUtil
fun updateArticles(newArticles: List<Article>) {
val diffCallback = ArticleDiffCallback(articles, newArticles)
val diffResult = DiffUtil.calculateDiff(diffCallback)
articles = newArticles
diffResult.dispatchUpdatesTo(this)
}
}
// Callback dla 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 używa algorytmu Eugene W. Myers' difference algorithm, który oblicza minimalną liczbę operacji (insert, remove, move, change) potrzebnych do przekształcenia starej listy w nową. Dla listy 100 elementów DiffUtil działa w 1–5 ms (Google, 2025). Dla dużych list (10000+) używaj AsyncListDiffer lub PagingDataAdapter z biblioteki Paging — wykonują obliczenia w tle.
ListView — poprzednik RecyclerView, pojawił się w Android 1.0 (API 1). Również wyświetla listy, ale nie narzuca ViewHolder, nie obsługuje LayoutManager i nie umie animować zmian. RecyclerView został zaprojektowany jako zamiennik ListView z bardziej rygorystyczną architekturą i wydajnością.
| Kryterium | RecyclerView | ListView |
|---|---|---|
| ViewHolder | Obowiązkowy (architektura) | Zalecany, ale nie obowiązkowy |
| LayoutManager | Podział: lista, siatka, kaskada | Tylko lista pionowa |
| Animacja zmian | DefaultItemAnimator od razu | Brak wbudowanego wsparcia |
| Powiadomienia o zmianach | Punktowe: notifyItemInserted/Removed | notifyDataSetChanged() tylko cała lista |
| DiffUtil | Wsparcie przez DiffUtil.Callback | Brak odpowiednika |
| Separatory | ItemDecoration (niestandardowe) | android:divider (wbudowany) |
| Minimalna wersja | API 14 (Android 4.0) przez AndroidX | API 1 (wszystkie wersje) |
Zalecenie Google (Android Developers, 2026): używaj RecyclerView dla wszystkich nowych projektów. ListView można pozostawić w starych bazach kodu (legacy), gdzie migracja nie uzasadnia kosztów. RecyclerView zapewnia lepszą wydajność na 100+ elementach, obsługuje przewijanie poziome i siatki bez dodatkowych obejść. W Android 15 ListView jest oficjalnie oznaczony jako częściowo przestarzały.
Często zadawane pytania
Sprawdź trzy warunki: (1) Adapter ustawiony przez recyclerView.adapter = adapter, (2) LayoutManager ustawiony przez recyclerView.layoutManager = LinearLayoutManager(context), (3) dane na liście nie są puste i adapter został powiadomiony (notifyDataSetChanged()). Typowy błąd — ustawienie danych po wywołaniu notifyDataSetChanged(). Dane powinny być przypisane przed powiadomieniem. Sprawdź rozmiar listy: adapter.itemCount powinien być większy niż 0.
Opóźnienia podczas przewijania (jank) występują, gdy onBindViewHolder() wykonuje ciężkie operacje: ładowanie obrazów bez buforowania, findViewById() wewnątrz bind, złożone obliczenia. Rozwiązania: (1) używaj biblioteki Glide lub Coil do asynchronicznego ładowania obrazów, (2) przenieś wszystkie findViewById() do konstruktora ViewHolder, (3) do obliczeń różnicowych używaj AsyncListDiffer. Włącz profilowanie GPU w Android Studio (Profile GPU Rendering), aby znaleźć wolne klatki.
Przekaż orientację do LayoutManager: LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false). Drugi parametr to reverseLayout (false = od lewej do prawej). Dla poziomej siatki używaj GridLayoutManager z orientacją poziomą lub niestandardowy LinearSnapHelper do przewijania stronicowego. Listy poziome dobrze nadają się do karuzel produktów, obrazów, kategorii.
RecyclerView nie ma wbudowanego onClickListener (w przeciwieństwie do ListView z onItemClickListener). Zaimplementuj go w ViewHolder: przekaż lambdę do konstruktora i ustaw itemView.setOnClickListener. Przykład: class ArticleViewHolder(itemView: View, val onItemClick: (Article) -> Unit) : RecyclerView.ViewHolder(itemView). Wywołuj onItemClick(article) wewnątrz bind(). Alternatywa — ustaw RecyclerView.addOnItemTouchListener() do obsługi gestów.
RecycledViewPool — wewnętrzna pula ViewHolderów, które stały się niewidoczne podczas przewijania. Zamiast niszczenia są przechowywane w puli i ponownie wykorzystywane. Domyślnie pula przechowuje 5 ViewHolderów każdego typu. Zwiększ rozmiar dla złożonych układów: recyclerView.getRecycledViewPool().setMaxRecycledViews(VIEW_TYPE_NORMAL, maxCount). Jeśli w RecyclerView jest kilka typów View (viewType), pula przechowuje osobne stosy dla każdego. Współdzielenie RecycledViewPool między różnymi RecyclerView oszczędza pamięć w ekranach z zagnieżdżonymi listami.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również