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 από τα top-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: δώστε ένα lambda στον κατασκευαστή και ορίστε 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. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης