RecyclerView est un composant Android Jetpack pour afficher efficacement de grands ensembles de données sous forme de listes et de grilles. Contrairement à ListView, RecyclerView réutilise les ViewHolders et gère la disposition via LayoutManager. Selon Google (Android Developers, 2026), RecyclerView traite jusqu'à 60 images par seconde lors du défilement d'une liste de plus de 1000 éléments grâce au pool de ViewHolders et à l'algorithme de différences DiffUtil. En savoir plus sur l'architecture des applications Android dans notre article sur ListView.
Points clés
RecyclerView est un composant de la bibliothèque Android Jetpack, présenté à Google I/O 2014 dans le cadre d'Android Support Library v7. Il est conçu pour afficher des listes et grilles dynamiques avec des performances élevées. RecyclerView est apparu comme un remplacement de ListView et résout le problème clé de son prédécesseur — l'absence de réutilisation forcée des ViewHolders.
L'architecture de RecyclerView repose sur le modèle Model-View-Adapter : les données sont stockées dans un modèle (List, LiveData, Flow), le rendu est géré par l'Adapter et la disposition est contrôlée par LayoutManager. Chaque composant est responsable de son domaine, rendant le système flexible et extensible.
Selon une enquête d'Android Developers (2025), RecyclerView est utilisé dans 78% des applications Android du top 100 du Google Play. Les performances de défilement pour une liste de plus de 1000 éléments restent stables à 60 FPS sur les appareils de milieu de gamme grâce à trois mécanismes : le pool de ViewHolders, le calcul de mise à jour basé sur les différences et le chargement asynchrone des données.
RecyclerView est optimal pour : les listes avec un nombre inconnu d'éléments (flux, chats, journaux), les grilles à N colonnes (galeries, catalogues), les dispositions en escalier (tableaux Pinterest) et les listes avec animations d'ajout/suppression. Pour les écrans simples avec 2 à 5 éléments, LinearLayout ou ScrollView sont plus simples et efficaces — RecyclerView ajoute une surcharge.
Pour utiliser RecyclerView, ajoutez la dépendance à build.gradle (app) : implementation 'androidx.recyclerview:recyclerview:1.4.0'. La version minimale de l'API est 14 (Android 4.0). À partir d'AndroidX RecyclerView 1.3.0, les modules recyclerview-selection pour la sélection d'éléments et recyclerview-swipe pour les gestes de balayage sont disponibles.
RecyclerView est divisé en trois couches indépendantes : l'Adapter gère les données et les ViewHolders, le LayoutManager s'occupe du positionnement des éléments et l'ItemAnimator contrôle les animations. Cette architecture permet de remplacer n'importe quelle couche sans modifier les autres — par exemple, passer de LinearLayoutManager à GridLayoutManager sans changer le code de l'Adapter.
| Composant | Responsabilités | Implémentation |
|---|---|---|
| Adapter | Créer des ViewHolders, lier les données à la View, notifier les changements | RecyclerView.Adapter<VH> |
| ViewHolder | Mettre en cache les références de View, éviter findViewById() | RecyclerView.ViewHolder |
| LayoutManager | Positionner les éléments, déterminer la direction de défilement | LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager |
| ItemAnimator | Animation pour ajouter, supprimer, déplacer des éléments | DefaultItemAnimator |
| ItemDecoration | Dessiner des séparateurs, espacements, fonds entre les éléments | RecyclerView.ItemDecoration |
Cycle de travail : (1) L'Adapter crée un ViewHolder via onCreateViewHolder(), (2) LayoutManager détermine la position du nouveau View à l'écran, (3) L'Adapter lie les données via onBindViewHolder(), (4) lors du défilement, les ViewHolders qui sortent de l'écran entrent dans le pool (RecycledViewPool) pour être réutilisés. Au lieu de créer de nouveaux Views, ceux existants sont réutilisés — c'est le mécanisme de performance clé.
ViewHolder est une classe qui stocke les références aux Views (TextView, ImageView, etc.) à l'intérieur d'un élément de liste. Sans ViewHolder, chaque défilement déclencherait findViewById() — l'une des opérations les plus coûteuses de l'interface Android. ViewHolder élimine cet appel en mettant en cache les références après la liaison initiale.
// Modèle de données
data class Article(
title: String,
summary: String,
author: String,
imageUrl: String?
)
// ViewHolder — stocke les références de 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
}
}
Règle pratique : chaque View dans la disposition de l'élément doit être déclarée comme propriété ViewHolder avec findViewById() exactement une fois — dans le constructeur. La méthode bind() n'appelle pas findViewById(), elle définit seulement le texte, les couleurs et les écouteurs. Cette approche accélère le défilement de la liste de 30 à 50% selon Google (Android Performance Patterns, 2025).
LayoutManager définit comment les éléments sont positionnés à l'intérieur de RecyclerView. Android fournit trois implémentations par défaut : LinearLayoutManager, GridLayoutManager et StaggeredGridLayoutManager. Le choix du LayoutManager affecte la perception des données et les performances de défilement.
| LayoutManager | Disposition | Quand l'utiliser | Méthode de configuration |
|---|---|---|---|
| LinearLayoutManager | Liste (verticale ou horizontale) | Flux, chats, notifications, listes de produits | LinearLayoutManager(context) |
| GridLayoutManager | Grille à N colonnes | Galeries, catalogues, icônes, grilles d'images | GridLayoutManager(context, spanCount) |
| StaggeredGridLayoutManager | Grille en escalier (hauteurs d'éléments différentes) | Tableaux Pinterest, notes, galeries masonry | StaggeredGridLayoutManager(spanCount, orientation) |
// Configurer LinearLayoutManager dans 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)
// Optimisation : taille fixe + pool
recyclerView.setHasFixedSize(true)
recyclerView.setItemViewCacheSize(20)
// Séparateur entre les éléments
val divider = DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
recyclerView.addItemDecoration(divider)
}
}
Performances : LinearLayoutManager avec défilement vertical est l'option la plus rapide car les éléments sont disposés séquentiellement et LayoutManager calcule les positions en O(1). GridLayoutManager nécessite des calculs supplémentaires pour la division en colonnes mais reste performant avec un spanCount fixe. StaggeredGridLayoutManager est le plus lent des trois car il doit calculer la position des éléments en cascade en tenant compte des hauteurs des éléments voisins. Utilisez-le uniquement lorsque des hauteurs de cellule visuellement différentes sont nécessaires.
Adapter dans RecyclerView implémente le modèle Adapter du GoF : il transforme les données (List<T>) en ViewHolders affichés à l'écran. Lorsque les données changent, l'Adapter peut notifier RecyclerView via notifyDataSetChanged(), notifyItemInserted() et des méthodes similaires. Le problème est que notifyDataSetChanged() redessine tous les éléments visibles, provoquant des à-coups (jank).
// Adapter avec DiffUtil pour des mises à jour efficaces
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
// Mettre à jour la liste via DiffUtil
fun updateArticles(newArticles: List<Article>) {
val diffCallback = ArticleDiffCallback(articles, newArticles)
val diffResult = DiffUtil.calculateDiff(diffCallback)
articles = newArticles
diffResult.dispatchUpdatesTo(this)
}
}
// Callback pour 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 utilise l'algorithme de différences d'Eugene W. Myers, qui calcule le nombre minimum d'opérations (insérer, supprimer, déplacer, modifier) pour transformer l'ancienne liste en la nouvelle. Pour une liste de 100 éléments, DiffUtil fonctionne en 1 à 5 ms (Google, 2025). Pour les grandes listes (10000+), utilisez AsyncListDiffer ou PagingDataAdapter de la bibliothèque Paging — ils effectuent les calculs dans un thread d'arrière-plan.
ListView est le prédécesseur de RecyclerView, introduit dans Android 1.0 (API 1). Il affiche également des listes mais n'impose pas ViewHolder, ne supporte pas LayoutManager et ne peut pas animer les changements. RecyclerView a été conçu comme un remplacement de ListView avec une architecture plus stricte et de meilleures performances.
| Critère | RecyclerView | ListView |
|---|---|---|
| ViewHolder | Obligatoire (architecture) | Recommandé mais pas obligatoire |
| LayoutManager | Séparation : liste, grille, escalier | Liste verticale uniquement |
| Animation des changements | DefaultItemAnimator intégré | Aucun support intégré |
| Notification des changements | Granulaire : notifyItemInserted/Removed | Seulement notifyDataSetChanged() pour toute la liste |
| DiffUtil | Support via DiffUtil.Callback | Pas d'équivalent |
| Séparateurs | ItemDecoration (personnalisé) | android:divider (intégré) |
| Version minimale | API 14 (Android 4.0) via AndroidX | API 1 (toutes versions) |
Recommandation de Google (Android Developers, 2026) : utilisez RecyclerView pour tous les nouveaux projets. ListView peut rester dans les bases de code existantes où la migration ne justifie pas le coût. RecyclerView offre de meilleures performances avec 100+ éléments, prend en charge le défilement horizontal et les grilles sans solutions de contournement supplémentaires. Dans Android 15, ListView est officiellement marqué comme partiellement obsolète.
Questions fréquentes
Vérifiez trois conditions : (1) l'Adapter est défini via recyclerView.adapter = adapter, (2) le LayoutManager est défini via recyclerView.layoutManager = LinearLayoutManager(context), (3) la liste de données n'est pas vide et l'Adapter a été notifié (notifyDataSetChanged()). Une erreur typique consiste à définir les données après avoir appelé notifyDataSetChanged(). Les données doivent être assignées avant la notification. Vérifiez la taille de la liste : adapter.itemCount doit être supérieur à 0.
La lenteur lors du défilement (jank) se produit lorsque onBindViewHolder() effectue des opérations lourdes : chargement d'images sans cache, findViewById() dans bind, calculs complexes. Solutions : (1) utilisez Glide ou Coil pour le chargement asynchrone d'images, (2) déplacez tous les appels findViewById() dans le constructeur ViewHolder, (3) pour les calculs de différences, utilisez AsyncListDiffer. Activez le profilage GPU dans Android Studio (Profile GPU Rendering) pour trouver les images lentes.
Passez l'orientation à LayoutManager : LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false). Le deuxième paramètre est reverseLayout (false = gauche à droite). Pour une grille horizontale, utilisez GridLayoutManager avec orientation horizontale ou un LinearSnapHelper personnalisé pour un défilement paginé. Les listes horizontales conviennent bien aux carrousels de produits, d'images et de catégories.
RecyclerView n'a pas d'onClickListener intégré (contrairement à ListView avec onItemClickListener). Implémentez-le dans ViewHolder : passez un lambda au constructeur et définissez itemView.setOnClickListener. Exemple : class ArticleViewHolder(itemView: View, val onItemClick: (Article) -> Unit) : RecyclerView.ViewHolder(itemView). Appelez onItemClick(article) dans bind(). Une alternative est d'utiliser RecyclerView.addOnItemTouchListener() pour la gestion des gestes.
RecycledViewPool est un pool interne de ViewHolders devenus invisibles lors du défilement. Au lieu d'être détruits, ils sont conservés dans le pool et réutilisés. Par défaut, le pool stocke 5 ViewHolders de chaque type. Augmentez la taille pour les dispositions complexes : recyclerView.getRecycledViewPool().setMaxRecycledViews(VIEW_TYPE_NORMAL, maxCount). Si un RecyclerView a plusieurs types de View (viewType), le pool maintient des piles séparées pour chacun. Partager un RecycledViewPool entre différents RecyclerViews économise de la mémoire sur les écrans avec des listes imbriquées.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi