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
RecycledViewPoolper 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; chiamisubmitList()- Invii sempre un nuovo riferimento alla lista, senza mai modificare la lista corrente
getChangePayload()per nuovi binding parzialiConcatAdapterper 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
- Collection di Kotlin
- Nozioni di base su RecyclerView
- Eventi di clic di RecyclerView
- SharedPreferences
- DataStore Preferences
- Adapter e DiffUtil