0Pricing
Android Academy · Aula

Adaptadores e DiffUtil

Crie adaptadores de lista eficientes e animados com ListAdapter e DiffUtil.ItemCallback. Use submitList() para atualizações parciais e fluidas.

Adaptadores e DiffUtil é uma aula grátis de Android Academy no CoddyKit. Esta é a aula 6 de 6. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Android Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Android Academy inclui 6 aulas no total.

O problema de notifyDataSetChanged()

Quando os dados do seu RecyclerView mudam, chamar notifyDataSetChanged() redesenha todos os itens — até mesmo os que não foram alterados. Isso causa:

  • Ausência de animações dos itens
  • Baixo desempenho em listas grandes
  • Piscadas na interface

DiffUtil calcula o conjunto mínimo de alterações e as aplica com animações suaves.

DiffUtil.ItemCallback

Defina como dois itens devem ser comparados. Há duas perguntas a responder:

  • areItemsTheSame — eles representam o mesmo item lógico? (normalmente, compare os IDs)
  • areContentsTheSame — todos os campos visíveis são 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 é um RecyclerView.Adapter com DiffUtil integrado. Passe seu ItemCallback ao construtor e chame submitList() para atualizar os dados:

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

Enviando novas listas

Chame submitList() sempre que seus dados mudarem. O ListAdapter compara a lista antiga com a nova em uma thread em segundo plano e aplica somente as alterações necessárias:

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

Listeners de clique no ListAdapter

Passe os manipuladores de clique pelo construtor — um padrão comum e organizado:

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() — atualizações parciais

Quando apenas um campo muda (por exemplo, a quantidade de curtidas), evite uma nova associação completa com 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)
    }
}

Vários tipos de visualização

Use getItemViewType() para renderizar diferentes layouts de item na mesma 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 — compondo listas

ConcatAdapter combina vários adaptadores em um único RecyclerView — ideal para cabeçalhos, rodapés e estados de carregamento:

// 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 — controle manual

Se você não puder estender ListAdapter, use AsyncListDiffer diretamente:

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
}

Dicas de desempenho

Mantenha seu RecyclerView rápido:

  • Use setHasStableIds(true) se seus itens tiverem IDs exclusivos e estáveis
  • Evite criar objetos dentro de onBindViewHolder
  • Use RecycledViewPool para compartilhar visualizações recicladas entre vários RecyclerViews
  • Use setItemViewCacheSize() para aumentar o cache de visualizações fora da tela

As animações já vêm prontas

A integração do DiffUtil no ListAdapter aciona automaticamente o DefaultItemAnimator para:

  • Aparecimento gradual de itens novos
  • Desaparecimento gradual de itens removidos
  • Animação de movimento quando um item muda de posição

Defina um animador personalizado: recyclerView.itemAnimator = MyAnimator(). Defina como null para desativar todas as animações.

Verificação rápida

Quais dois métodos você deve implementar em DiffUtil.ItemCallback?

Recapitulação: adaptadores e DiffUtil

Listas eficientes e animadas com o mínimo de código:

  • DiffUtil.ItemCallback — defina a identidade (areItemsTheSame) e a igualdade (areContentsTheSame)
  • ListAdapter — comparação assíncrona integrada; chame submitList()
  • Sempre envie uma nova referência de lista; nunca altere a lista atual
  • getChangePayload() para novas associações parciais
  • ConcatAdapter para compor vários adaptadores

A seguir: navegação entre várias telas com o Navigation Component.

Perguntas Frequentes

A aula “Adaptadores e DiffUtil” é grátis?

Sim — o texto completo de “Adaptadores e DiffUtil” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Android Academy, atualize para CoddyKit PRO. O curso de Android Academy inclui 6 aulas no total.

O que vou aprender em “Adaptadores e DiffUtil”?

Crie adaptadores de lista eficientes e animados com ListAdapter e DiffUtil.ItemCallback. Use submitList() para atualizações parciais e fluidas. Você pratica Android Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar Android Academy?

Nenhuma experiência prévia é necessária. Android Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 6 de 6.

Quanto tempo leva a aula “Adaptadores e DiffUtil”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de Android Academy?

Sim. Cada aula de Android Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Coleções do Kotlin
  2. Noções Básicas de RecyclerView
  3. Eventos de clique do RecyclerView
  4. SharedPreferences
  5. Preferências do DataStore
  6. Adaptadores e DiffUtil
← Voltar para Android Academy