Android Academy · Leçon

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.

Leçon 6 sur 613 étapes

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 RecycledViewPool pour 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&nbsp;: 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 ; appelez submitList()
  • Transmettez toujours une nouvelle référence de liste : ne modifiez jamais la liste actuelle
  • getChangePayload() pour les liaisons partielles
  • ConcatAdapter pour composer plusieurs adaptateurs

Ensuite : navigation entre plusieurs écrans avec le composant Navigation.

Gratuit pour commencer

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

  1. Collections Kotlin
  2. Bases de RecyclerView
  3. Événements de clic dans RecyclerView
  4. SharedPreferences
  5. Préférences DataStore
  6. Adaptateurs et DiffUtil
← Retour à Android Academy