Adaptateurs et DiffUtil
Créez des adaptateurs de listes efficaces et animés avec ListAdapter et DiffUtil.ItemCallback. Utilisez submitList() pour effectuer des mises à jour fluides et partielles.
Adaptateurs et DiffUtil est une leçon Android Academy gratuite sur CoddyKit. Ceci est la leçon 6 sur 6. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Android Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Android Academy comprend 6 leçons au total.
Le problème de notifyDataSetChanged()
Lorsque les données de votre RecyclerView changent, l'appel à notifyDataSetChanged() redessine chaque élément, même ceux qui n'ont pas changé. Cela entraîne :
- Aucune animation des éléments
- De mauvaises performances avec les grandes listes
- Des scintillements de l'interface
DiffUtil calcule l'ensemble minimal des changements et les applique avec des animations fluides.
DiffUtil.ItemCallback
Définissez la manière dont deux éléments sont comparés. Vous devez répondre à deux questions :
areItemsTheSame— représentent-ils le même élément logique ? (on compare généralement leurs identifiants)areContentsTheSame— tous les champs visibles sont-ils identiques ?
data class Post(val id: Int, val title: String, val likes: Int)
class PostDiffCallback : DiffUtil.ItemCallback<Post>() {
override fun areItemsTheSame(oldItem: Post, newItem: Post): Boolean {
return oldItem.id == newItem.id // same post?
}
override fun areContentsTheSame(oldItem: Post, newItem: Post): Boolean {
return oldItem == newItem // data class equality (all fields)
}
}ListAdapter
ListAdapter est un RecyclerView.Adapter qui intègre DiffUtil. Transmettez-lui votre ItemCallback dans son constructeur et appelez submitList() pour mettre à jour les données :
class PostAdapter : ListAdapter<Post, PostAdapter.ViewHolder>(PostDiffCallback()) {
inner class ViewHolder(val binding: ItemPostBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ItemPostBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val post = getItem(position) // use getItem(), not a manual list
holder.binding.tvTitle.text = post.title
holder.binding.tvLikes.text = "${post.likes} likes"
}
}Soumission de nouvelles listes
Appelez submitList() chaque fois que vos données changent. ListAdapter compare l'ancienne et la nouvelle liste sur un thread d'arrière-plan et applique uniquement les changements nécessaires :
// In Activity or Fragment:
val adapter = PostAdapter()
binding.recyclerView.adapter = adapter
// Observe ViewModel LiveData:
viewModel.posts.observe(viewLifecycleOwner) { posts ->
adapter.submitList(posts) // diff calculated off main thread
}
// Important: always submit a NEW list, not a mutated old one
// WRONG: currentList.add(newPost); adapter.submitList(currentList)
// RIGHT: adapter.submitList(currentList + newPost)Écouteurs de clic dans ListAdapter
Transmettez les gestionnaires de clic via le constructeur — il s'agit d'une pratique courante et claire :
class PostAdapter(
private val onLike: (Post) -> Unit,
private val onOpen: (Post) -> Unit
) : ListAdapter<Post, PostAdapter.ViewHolder>(PostDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val post = getItem(position)
holder.binding.tvTitle.text = post.title
holder.binding.btnLike.setOnClickListener { onLike(post) }
holder.itemView.setOnClickListener { onOpen(post) }
}
}
// Usage:
val adapter = PostAdapter(
onLike = { viewModel.likePost(it) },
onOpen = { navigateToDetail(it.id) }
)getChangePayload() — mises à jour partielles
Lorsqu'un seul champ change (par exemple, le nombre de mentions J'aime), évitez une nouvelle liaison complète avec getChangePayload() :
class PostDiffCallback : DiffUtil.ItemCallback<Post>() {
override fun areItemsTheSame(oldItem: Post, newItem: Post) = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Post, newItem: Post) = oldItem == newItem
override fun getChangePayload(oldItem: Post, newItem: Post): Any? {
// Return non-null payload to trigger partial bind
return if (oldItem.likes != newItem.likes) "likes_changed" else null
}
}
// In Adapter.onBindViewHolder with payloads:
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: List<Any>) {
if (payloads.contains("likes_changed")) {
holder.binding.tvLikes.text = "${getItem(position).likes} likes"
} else {
super.onBindViewHolder(holder, position, payloads)
}
}Plusieurs types d'éléments
Utilisez getItemViewType() pour afficher différentes mises en page d'éléments dans une même liste :
class FeedAdapter : ListAdapter<FeedItem, RecyclerView.ViewHolder>(FeedDiffCallback()) {
companion object {
const val TYPE_POST = 0
const val TYPE_AD = 1
}
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is FeedItem.Post -> TYPE_POST
is FeedItem.Ad -> TYPE_AD
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {
TYPE_POST -> PostViewHolder(ItemPostBinding.inflate(LayoutInflater.from(parent.context), parent, false))
else -> AdViewHolder(ItemAdBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
}ConcatAdapter — composition de listes
ConcatAdapter combine plusieurs adaptateurs en un seul RecyclerView — idéal pour les en-têtes, les pieds de page et les états de chargement :
// Separate adapters for different sections
val headerAdapter = HeaderAdapter()
val postAdapter = PostAdapter()
val loaderAdapter = LoadingAdapter()
// Combine them in order
binding.recyclerView.adapter = ConcatAdapter(headerAdapter, postAdapter, loaderAdapter)
// Update each independently
postAdapter.submitList(posts)
loaderAdapter.showLoading(true)AsyncListDiffer — contrôle manuel
Si vous ne pouvez pas étendre ListAdapter, utilisez directement AsyncListDiffer :
class CustomAdapter : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
private val differ = AsyncListDiffer(this, PostDiffCallback())
fun submitList(list: List<Post>) = differ.submitList(list)
fun getItem(position: Int): Post = differ.currentList[position]
override fun getItemCount() = differ.currentList.size
}Conseils de performance
Gardez votre RecyclerView rapide :
- Utilisez
setHasStableIds(true)si vos éléments possèdent des identifiants uniques et stables - Évitez de créer des objets dans
onBindViewHolder - Utilisez
RecycledViewPoolpour partager les vues recyclées entre plusieurs RecyclerViews - Utilisez
setItemViewCacheSize()pour augmenter le cache hors écran
Les animations sont automatiques
L'intégration de DiffUtil dans ListAdapter déclenche automatiquement DefaultItemAnimator pour :
- Un fondu d'entrée pour les nouveaux éléments
- Un fondu de sortie pour les éléments supprimés
- Une animation de déplacement lorsqu'un élément change de position
Définissez un animateur personnalisé : recyclerView.itemAnimator = MyAnimator(). Définissez sa valeur sur null pour désactiver toutes les animations.
Vérification rapide
Quelles sont les deux méthodes que vous devez implémenter dans DiffUtil.ItemCallback ?
Récapitulatif : adaptateurs et DiffUtil
Des listes efficaces et animées avec un minimum de code :
DiffUtil.ItemCallback— définissez l'identité (areItemsTheSame) et l'égalité (areContentsTheSame)ListAdapter— comparaison asynchrone intégrée ; appelezsubmitList()- Transmettez toujours une nouvelle référence de liste : ne modifiez jamais la liste actuelle
getChangePayload()pour les liaisons partiellesConcatAdapterpour composer plusieurs adaptateurs
Ensuite : navigation entre plusieurs écrans avec le composant Navigation.
Apprends Kotlin avec un tuteur IA — gratuit
Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.
- Cours
- 36
- Leçons
- 152
Questions Fréquemment Posées
La leçon « Adaptateurs et DiffUtil » est-elle gratuite ?
Oui — le texte complet de « Adaptateurs et DiffUtil » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Android Academy, passe à CoddyKit PRO. Le cours Android Academy comprend 6 leçons au total.
Qu'est-ce que j'apprendrai dans « Adaptateurs et DiffUtil » ?
Créez des adaptateurs de listes efficaces et animés avec ListAdapter et DiffUtil.ItemCallback. Utilisez submitList() pour effectuer des mises à jour fluides et partielles. Tu pratiques Android Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Android Academy ?
Aucune expérience préalable n'est requise. Android Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 6 sur 6.
Combien de temps prend la leçon « Adaptateurs et DiffUtil » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Android Academy ?
Oui. Chaque leçon Android Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Collections Kotlin
- Bases de RecyclerView
- Événements de clic dans RecyclerView
- SharedPreferences
- Préférences DataStore
- Adaptateurs et DiffUtil