0Pricing
Android Academy · Lección

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 RecycledViewPool para 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 a submitList()
  • Envíe siempre una nueva referencia de lista; nunca modifique la lista actual
  • Use getChangePayload() para volver a enlazar parcialmente los elementos
  • Use ConcatAdapter para 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

  1. Colecciones de Kotlin
  2. Fundamentos de RecyclerView
  3. Eventos de clic de RecyclerView
  4. SharedPreferences
  5. DataStore Preferences
  6. Adaptadores y DiffUtil
← Volver a Android Academy