0Pricing
Android Academy · Lezione

Adapter e DiffUtil

Costruisca adapter efficienti e animati per gli elenchi con ListAdapter e DiffUtil.ItemCallback. Usi submitList() per aggiornamenti fluidi e parziali.

Adapter e DiffUtil è una lezione Android Academy gratuita su CoddyKit. Questa è la lezione 6 di 6. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Android Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Android Academy include 6 lezioni in totale.

Il problema di notifyDataSetChanged()

Quando cambiano i dati di RecyclerView, la chiamata a notifyDataSetChanged() ridisegna ogni elemento, anche quelli invariati. Questo comporta:

  • Nessuna animazione degli elementi
  • Prestazioni scarse con le liste di grandi dimensioni
  • Interfaccia utente soggetta a sfarfallii

DiffUtil calcola l'insieme minimo di modifiche e lo applica con animazioni fluide.

DiffUtil.ItemCallback

Definisca come confrontare due elementi. Occorre rispondere a due domande:

  • areItemsTheSame — rappresentano lo stesso elemento logico? (in genere si confrontano gli ID)
  • areContentsTheSame — tutti i campi visibili sono identici?
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 è un RecyclerView.Adapter con DiffUtil integrato. Passi il proprio ItemCallback al costruttore e chiami submitList() per aggiornare i dati:

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"
    }
}

Invio di nuove liste

Chiami submitList() ogni volta che cambiano i dati. ListAdapter confronta la lista precedente e quella nuova in un thread in background e applica solo le modifiche necessarie:

// 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)

Listener dei clic in ListAdapter

Passi i gestori dei clic tramite il costruttore: è un approccio comune e ordinato:

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() — Aggiornamenti parziali

Quando cambia un solo campo (ad esempio, il numero di Mi piace), eviti di eseguire un nuovo binding completo con 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)
    }
}

Più tipi di visualizzazione

Usi getItemViewType() per visualizzare layout diversi per gli elementi della stessa lista:

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 — Composizione di liste

ConcatAdapter combina più adapter in un unico RecyclerView: è ideale per intestazioni, piè di pagina e stati di caricamento:

// 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 — Controllo manuale

Se non può estendere ListAdapter, usi direttamente 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
}

Suggerimenti sulle prestazioni

Mantenga veloce il proprio RecyclerView:

  • Usi setHasStableIds(true) se gli elementi hanno ID univoci e stabili
  • Eviti di creare oggetti all'interno di onBindViewHolder
  • Usi RecycledViewPool per condividere le view riciclate tra più RecyclerView
  • Usi setItemViewCacheSize() per aumentare la cache fuori schermo

Le animazioni sono incluse

L'integrazione di DiffUtil in ListAdapter attiva automaticamente DefaultItemAnimator per:

  • Comparsa graduale dei nuovi elementi
  • Scomparsa graduale degli elementi rimossi
  • Animazione dello spostamento quando un elemento cambia posizione

Imposti un animator personalizzato: recyclerView.itemAnimator = MyAnimator(). Imposti null per disabilitare tutte le animazioni.

Verifica rapida

Quali due metodi è necessario implementare in DiffUtil.ItemCallback?

Riepilogo: adapter e DiffUtil

Liste efficienti e animate con una quantità minima di codice:

  • DiffUtil.ItemCallback — definisce identità (areItemsTheSame) e uguaglianza (areContentsTheSame)
  • ListAdapter — confronto asincrono integrato; chiami submitList()
  • Invii sempre un nuovo riferimento alla lista, senza mai modificare la lista corrente
  • getChangePayload() per nuovi binding parziali
  • ConcatAdapter per comporre più adapter

Prossimo argomento: navigazione tra più schermate con Navigation Component.

Domande Frequenti

La lezione «Adapter e DiffUtil» è gratuita?

Sì — il testo completo di «Adapter e DiffUtil» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Android Academy, passa a CoddyKit PRO. Il corso Android Academy include 6 lezioni in totale.

Cosa imparerò in «Adapter e DiffUtil»?

Costruisca adapter efficienti e animati per gli elenchi con ListAdapter e DiffUtil.ItemCallback. Usi submitList() per aggiornamenti fluidi e parziali. Eserciti Android Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Android Academy?

Non è richiesta alcuna esperienza precedente. Android Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 6 di 6.

Quanto tempo richiede la lezione «Adapter e DiffUtil»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Android Academy?

Sì. Ogni lezione Android Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Collection di Kotlin
  2. Nozioni di base su RecyclerView
  3. Eventi di clic di RecyclerView
  4. SharedPreferences
  5. DataStore Preferences
  6. Adapter e DiffUtil
← Torna a Android Academy