Adaptadores y DiffUtil
Cree adaptadores de listas eficientes y animados con ListAdapter y DiffUtil.ItemCallback. Use submitList() para realizar actualizaciones parciales y fluidas.
Adaptadores y DiffUtil es una lección gratuita de Android Academy en CoddyKit. Esta es la lección 6 de 6. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Android Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Android Academy incluye 6 lecciones en total.
El problema de notifyDataSetChanged()
Cuando cambian los datos de su RecyclerView, llamar a notifyDataSetChanged() vuelve a dibujar cada elemento, incluso los que no han cambiado. Esto provoca:
- Ausencia de animaciones de elementos
- Bajo rendimiento en listas grandes
- Parpadeos en la interfaz
DiffUtil calcula el conjunto mínimo de cambios y los aplica con animaciones fluidas.
DiffUtil.ItemCallback
Defina cómo se comparan dos elementos. Debe responder a dos preguntas:
areItemsTheSame— ¿representan el mismo elemento lógico? (normalmente, se comparan los ID)areContentsTheSame— ¿todos los campos visibles son idénticos?
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 es un RecyclerView.Adapter que incluye DiffUtil. Pase su ItemCallback a su constructor y llame a submitList() para actualizar los datos:
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"
}
}Enviar listas nuevas
Llame a submitList() cada vez que cambien sus datos. ListAdapter compara la lista anterior y la nueva en un hilo en segundo plano y aplica únicamente los cambios necesarios:
// 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)Detectores de clics en ListAdapter
Pase los controladores de clics mediante el constructor; es un patrón habitual y limpio:
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() — Actualizaciones parciales
Cuando solo cambia un campo (por ejemplo, el número de «me gusta»), evite volver a enlazar todo el elemento mediante 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)
}
}Varios tipos de vista
Use getItemViewType() para mostrar diferentes diseños de elementos en la misma 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 — Componer listas
ConcatAdapter combina varios adaptadores en un único RecyclerView; es ideal para encabezados, pies de página y estados de carga:
// 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 — Control manual
Si no puede extender ListAdapter, use AsyncListDiffer directamente:
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
}Consejos de rendimiento
Mantenga su RecyclerView rápido:
- Use
setHasStableIds(true)si sus elementos tienen ID únicos y estables - Evite crear objetos dentro de
onBindViewHolder - Use
RecycledViewPoolpara compartir vistas recicladas entre varios RecyclerView - Use
setItemViewCacheSize()para aumentar la caché fuera de pantalla
Las animaciones vienen incluidas
La integración de DiffUtil en ListAdapter activa automáticamente DefaultItemAnimator para:
- Aparición gradual de elementos nuevos
- Desaparición gradual de elementos eliminados
- Animación de desplazamiento cuando cambia la posición de un elemento
Establezca un animador personalizado: recyclerView.itemAnimator = MyAnimator(). Establézcalo en null para desactivar todas las animaciones.
Comprobación rápida
¿Qué dos métodos debe implementar en DiffUtil.ItemCallback?
Repaso: adaptadores y DiffUtil
Listas eficientes y animadas con un mínimo de código:
DiffUtil.ItemCallback— defina la identidad (areItemsTheSame) y la igualdad (areContentsTheSame)ListAdapter— comparación asíncrona integrada; llame asubmitList()- Envíe siempre una nueva referencia de lista; nunca modifique la lista actual
- Use
getChangePayload()para volver a enlazar parcialmente los elementos - Use
ConcatAdapterpara combinar varios adaptadores
A continuación: navegación entre varias pantallas con Navigation Component.
Preguntas frecuentes
¿La lección «Adaptadores y DiffUtil» es gratis?
Sí — el texto completo de «Adaptadores y DiffUtil» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Android Academy, actualiza a CoddyKit PRO. El curso de Android Academy incluye 6 lecciones en total.
¿Qué aprenderé en «Adaptadores y DiffUtil»?
Cree adaptadores de listas eficientes y animados con ListAdapter y DiffUtil.ItemCallback. Use submitList() para realizar actualizaciones parciales y fluidas. Practicas Android Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Android Academy?
No se requiere experiencia previa. Android Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 6 de 6.
¿Cuánto tiempo toma la lección «Adaptadores y DiffUtil»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Android Academy?
Sí. Cada lección de Android Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Colecciones de Kotlin
- Fundamentos de RecyclerView
- Eventos de clic de RecyclerView
- SharedPreferences
- DataStore Preferences
- Adaptadores y DiffUtil