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
RecycledViewPoolpara 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; chamesubmitList()- Sempre envie uma nova referência de lista; nunca altere a lista atual
getChangePayload()para novas associações parciaisConcatAdapterpara 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
- Coleções do Kotlin
- Noções Básicas de RecyclerView
- Eventos de clique do RecyclerView
- SharedPreferences
- Preferências do DataStore
- Adaptadores e DiffUtil