No tutorial anterior, você atualizou o aplicativo TrackMySleepQuality para exibir dados sobre a qualidade do
sono em um RecyclerView
. As técnicas que você aprendeu ao construir seu primeiro
RecyclerView
são suficientes para a maioria dos RecyclerViews
que exibem listas
simples que não são muito grandes. No entanto, existem várias técnicas que tornam o RecyclerView
mais eficiente para listas grandes e tornam seu código mais fácil de manter e estender para listas e grades
complexas.
Neste tutorial, você desenvolve o aplicativo sleep-tracker do tutorial anterior. Você aprende uma maneira mais
eficaz de atualizar a lista de dados do sono e como usar a vinculação de dados com RecyclerView
.
(Se você não tem o aplicativo do tutorial anterior, pode baixar o código inicial para este tutorial).
safeArgs
para passar dados entre fragmentos.LiveData
e seus
observadores. Room
, criar um DAO e definir entidades.RecyclerView
básico com um Adapter
, ViewHolder
e
layout de item. DiffUtil
para atualizar de forma eficiente uma lista exibida pelo
RecyclerView
. RecyclerView
. SleepNightAdapter
para atualizar a lista de forma eficiente usando
DiffUtil
.RecyclerView
, usando adaptadores de vinculação para
transformar os dados.O aplicativo sleep-tracker possui duas telas, representadas por fragmentos, conforme mostrado na figura abaixo.
A primeira tela, mostrada à esquerda, possui botões para iniciar e parar o rastreamento. A tela mostra alguns dos dados de sono do usuário. O botão Clear exclui permanentemente todos os dados que o aplicativo coletou para o usuário. A segunda tela, mostrada à direita, é para selecionar uma classificação de qualidade do sono.
Este aplicativo foi arquitetado para usar um controlador de IU, ViewModel
e LiveData
,
e um banco de dados Room
para persistir os dados de sono.
Os dados do sono são exibidos em um RecyclerView
. Neste tutorial, você constrói o
DiffUtil
e a parte de vinculação de dados para o RecyclerView
. Após este tutorial, seu
aplicativo terá exatamente a mesma aparência, mas será mais eficiente e mais fácil de escalar e manter.
Você pode continuar usando o aplicativo SleepTracker do tutorial anterior ou pode baixar o aplicativo RecyclerViewDiffUtilDataBinding-Starter do GitHub.
SleepNightAdapter.kt
.RecyclerView
com o padrão do adaptador para exibir os dados de sono para
o usuário. SleepNight
. Cada objeto
SleepNight
representa uma única noite de sono, sua duração e qualidade.SleepNightAdapter
adapta a lista de objetos SleepNight
em algo que o
RecyclerView
pode usar e exibir.SleepNightAdapter
produz ViewHolders
que contêm as vistas, dados e
informações meta para a vista do reciclador para exibir os dados.RecyclerView
usa o SleepNightAdapter
para determinar quantos itens há para exibir
(getItemCount()
). RecyclerView
usa onCreateViewHolder()
e
onBindViewHolder()
para obter os recipientes de vistas vinculados aos dados para exibição.Para informar ao RecyclerView
que um item da lista foi alterado e precisa ser atualizado, o código
atual chama notifyDataSetChanged()
no SleepNightAdapter
, conforme mostrado
abaixo.
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}
No entanto, notifyDataSetChanged()
diz ao RecyclerView
que a lista inteira é
potencialmente inválida. Como resultado, o RecyclerView
vincula e redesenha todos os itens da
lista, incluindo itens que não são visíveis na tela. É muito trabalho desnecessário. Para listas grandes ou
complexas, esse processo pode demorar o suficiente para que a tela pisque ou gagueje conforme o usuário rola a
lista.
Para corrigir esse problema, você pode dizer ao RecyclerView
exatamente o que mudou.
RecyclerView
pode então atualizar apenas as vistas que mudaram na tela.
RecyclerView
tem uma API rica para atualizar um único elemento. Você pode usar notifyItemChanged()
para informar ao RecyclerView
que um item
foi alterado, e você pode usar funções semelhantes para itens que são adicionados, removidos, ou movido. Você
poderia fazer tudo manualmente, mas essa tarefa não seria trivial e envolveria um pouco de código.
Felizmente, existe uma maneira melhor.
RecyclerView
tem uma classe chamada DiffUtil
que é para calcular as diferenças entre
duas listas. DiffUtil
pega uma lista antiga e uma nova lista e descobre o que é diferente. Ele
encontra itens que foram adicionados, removidos ou alterados. Em seguida, ele usa um algoritmo chamado algoritmo de diferença de Eugene W. Myers para
descobrir o número mínimo de alterações a serem feitas na lista antiga para produzir a nova lista.
Depois que DiffUtil
descobre o que mudou, RecyclerView
pode usar essas informações
para atualizar apenas os itens que foram alterados, adicionados, removidos ou movidos, o que é muito mais
eficiente do que refazer a lista inteira.
Nesta tarefa, você atualiza o SleepNightAdapter
para usar o DiffUtil
para otimizar o
RecyclerView
para alterações nos dados.
Para usar a funcionalidade da classe DiffUtil
, estenda DiffUtil.ItemCallback
.
SleepNightAdapter.kt
.SleepNightAdapter
, crie uma classe de nível
superior chamada SleepNightDiffCallback
que estende DiffUtil.ItemCallback
. Passe
SleepNight
como um parâmetro genérico. class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {
}
SleepNightDiffCallback
. Alt+Enter
(Option+Enter
no Mac) e
selecione Implement Members. areItemsTheSame()
e areContentsTheSame()
e clique em
OK.SleepNightDiffCallback
para os dois
métodos, conforme mostrado abaixo. DiffUtil
usa esses dois métodos para descobrir como a lista e
os itens foram alterados. override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
areItemsTheSame()
, substitua o TODO
pelo código que testa se os dois
itens SleepNight
passados, oldItem
e newItem
são iguais. Se os itens
tiverem o mesmo nightId
, eles são o mesmo item, então retorne true
. Caso contrário,
retorne false
. DiffUtil
usa este teste para ajudar a descobrir se um item foi
adicionado, removido ou movido. override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem.nightId == newItem.nightId
}
areContentsTheSame()
, verifique se oldItem
e newItem
contêm
os mesmos dados; isto é, se eles são iguais. Esta verificação de igualdade verificará todos os campos, pois
SleepNight
é uma classe de dados. As classes Data
definem automaticamente
equals
e alguns outros métodos. Se houver diferenças entre oldItem
e
newItem
, este código informa a DiffUtil
que o item foi atualizado.override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem == newItem
}
É um padrão comum usar um RecyclerView
para exibir uma lista que muda. RecyclerView
fornece uma classe de adaptador, ListAdapter
, que ajuda a construir um adaptador
RecyclerView
apoiado por uma lista.
ListAdapter
mantém o controle da lista e notifica o adaptador quando a lista é atualizada.
SleepNightAdapter.kt
, altere a assinatura da classe de
SleepNightAdapter
para estender o ListAdapter
. androidx.recyclerview.widget.ListAdapter
.SleepNight
como o primeiro argumento para o ListAdapter
, antes de
SleepNightAdapter.ViewHolder
. SleepNightDiffCallback()
como um parâmetro para o construtor. O
ListAdapter
usará isso para descobrir o que mudou na lista. Sua assinatura de classe
SleepNightAdapter
finalizada deve ser semelhante à mostrada abaixo.class SleepNightAdapter : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
SleepNightAdapter
, exclua o campo data
, incluindo o configurador.
Você não precisa mais dele, pois ListAdapter
mantém o controle da lista. getItemCount()
, pois o ListAdapter
implementa esse
método.onBindViewHolder()
, altere a variável item
. Em vez de
usar data
para obter um item
, chame o método getItem(position)
que o
ListAdapter
fornece.val item = getItem(position)
Etapa 2: Use submitList() para manter a lista atualizada
Seu código precisa informar ao ListAdapter
quando uma lista alterada está disponível.
ListAdapter
fornece um método chamado submitList()
para informar ao
ListAdapter
que uma nova versão da lista está disponível. Quando este método é chamado, o
ListAdapter
difere a nova lista da antiga e detecta itens que foram adicionados, removidos, movidos
ou alterados. Em seguida, o ListAdapter
atualiza os itens mostrados por RecyclerView
.
SleepTrackerFragment.kt
. onCreateView()
, no observador em sleepTrackerViewModel
, encontre o erro onde a
variável data
que você excluiu é referenciada.adapter.data = it
por uma chamada para adapter.submitList(it)
. O código
atualizado é mostrado abaixo.sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
Nesta tarefa, você usa a mesma técnica dos tutoriais anteriores para configurar a vinculação de dados e elimina
chamadas para findViewById()
.
list_item_sleep_night.xml
na guia Text. ConstraintLayout
e pressione Alt+Enter
(Option+Enter
em um Mac). O menu de intenção (o menu "solução rápida") é aberto.
<layout>
e adiciona uma etiqueta <data>
dentro.<data>
, declare uma
variável chamada sleep
.type
o nome qualificado de SleepNight
,
com.example.android.trackmysleepquality.database.SleepNight
. Sua etiqueta
<data>
finalizada deve se parecer com a mostrada abaixo. <data>
<variable
name="sleep"
type="com.example.android.trackmysleepquality.database.SleepNight"/>
</data>
Binding
, selecione Build > Clean Project e,
em seguida, selecione Build > Rebuild Project. (Se você ainda tiver problemas, selecione
File > Invalidate Caches / Restart). O objeto de vinculação
ListItemSleepNightBinding
, junto com o código relacionado, é adicionado aos arquivos gerados do
projeto.SleepNightAdapter.kt
.ViewHolder
, encontre o método from()
.view
.Código para delete:
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night, parent, false)
view
estava, defina uma nova variável chamada binding
que infla o
objeto de vinculação ListItemSleepNightBinding
, conforme mostrado abaixo. Faça a importação
necessária do objeto de vinculação.val binding =
ListItemSleepNightBinding.inflate(layoutInflater, parent, false)
view
, retorne binding
. return ViewHolder(binding)
binding
. Pressione Alt+Enter
(Option+Enter
em um Mac) para abrir o menu de intenção. ViewHolder
.
ViewHolder
para ver a mudança na assinatura. Você
vê um erro para itemView
, pois alterou itemView
para binding
no método
from()
. ViewHolder
, clique com o botão direito em uma
das ocorrências de itemView
e selecione Refactor> Rename.
Mude o nome para binding
.binding
com val
para torná-lo uma propriedade.
RecyclerView.ViewHolder
, altere o parâmetro de
binding
para binding.root
. Você precisa passar uma View
, e
binding.root
é a raiz ConstraintLayout
em seu layout de item. class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root){
Você também vê um erro nas chamadas para findViewById()
e corrige isso a seguir.
Agora você pode atualizar as propriedades sleepLength
, quality
e
qualityImage
para usar o objeto binding
em vez de findViewById()
.
sleepLength
, qualityString
e qualityImage
para usar as vistas do objeto binding
, conforme mostrado abaixo. Depois disso, seu código não
deve mostrar mais erros.val sleepLength: TextView = binding.sleepLength
val quality: TextView = binding.qualityString
val qualityImage: ImageView = binding.qualityImage
Com o objeto de vinculação no lugar, você não precisa mais definir as propriedades sleepLength
,
quality
e qualityImage
. DataBinding
armazenará em cache as pesquisas,
portanto, não há necessidade de declarar essas propriedades.
sleepLength
, quality
e
qualityImage
. Selecione Refactor > Inline ou pressione
Control+Command+N
(Option+Command+N
em um Mac).Nesta tarefa, você atualiza seu aplicativo para usar vinculação de dados com adaptadores de vinculação para definir os dados em suas vistas.
Em um tutorial anterior, você usou a classe Transformations
para pegar o LiveData
e gerar strings formatadas
para exibir em vistas de texto. No entanto, se você precisar vincular diferentes tipos ou tipos complexos,
poderá fornecer adaptadores de vinculação para ajudar a vinculação de dados a usar esses tipos. Adaptadores de
vinculação são adaptadores que pegam seus dados e os adaptam em algo que a vinculação de dados pode usar para
vincular uma vista, como texto ou imagem.
Você implementará três adaptadores de vinculação, um para a imagem de qualidade e um para cada campo de texto.
Em resumo, para declarar um adaptador de vinculação, você define um método que pega um item e uma vista e
anota-o com @BindingAdapter
. No corpo do método, você implementa a transformação. No Kotlin, você
pode escrever um adaptador de vinculação como uma função de extensão na classe de vistas que recebe os dados.
Observe que você terá que importar várias classes na etapa e não será chamado individualmente.
SleepNightAdapater.kt
.ViewHolder
, encontre o método bind()
e lembre-se do que esse
método faz. Você pegará o código que calcula os valores de binding.sleepLength
,
binding.quality
e binding.qualityImage
e o usará dentro do adaptador em vez de. (Por
enquanto, deixe o código como está; mova-o em uma etapa posterior).sleeptracker
, crie e abra um arquivo chamado BindingUtils.kt
.TextView
, chamada setSleepDurationFormatted
, e
transmita um SleepNight
. Esta função será o seu adaptador para calcular e formatar a duração do
sono. fun TextView.setSleepDurationFormatted(item: SleepNight) {}
setSleepDurationFormatted
, vincule os dados à vista como você fez em
ViewHolder.bind()
. Chame convertDurationToFormatted()
e, a seguir, defina o
text
de TextView
como o texto formatado. (Como esta é uma função de extensão em
TextView
, você pode acessar diretamente a propriedade text
).text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, context.resources)
@BindingAdapter
.
sleepDurationFormatted
, então passe
sleepDurationFormatted
como um argumento para @BindingAdapter
.@BindingAdapter("sleepDurationFormatted")
SleepNight
. Crie
uma função de extensão chamada setSleepQualityString()
em TextView
e passe um
SleepNight
. ViewHolder.bind()
. Chame
convertNumericQualityToString
e defina o text
. @BindingAdapter("sleepQualityString")
.@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight) {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
ImageView
, chame setSleepImage
e use o código de ViewHolder.bind()
,
conforme mostrado abaixo. @BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight) {
setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
SleepNightAdapter.kt
. bind()
, pois agora você pode usar a vinculação de dados e seus novos
adaptadores para fazer este trabalho.fun bind(item: SleepNight) {
}
bind()
, atribua sleep para item
, pois você precisa informar ao objeto de
vinculação sobre seu novo SleepNight
. binding.sleep = item
binding.executePendingBindings()
. Essa chamada é uma otimização
que solicita que a vinculação de dados execute imediatamente qualquer vinculação pendente. É sempre uma boa
ideia chamar executePendingBindings()
quando você usa adaptadores de vinculação em um
RecyclerView
, pois pode acelerar um pouco o dimensionamento das vistas. binding.executePendingBindings()
list_item_sleep_night.xml
.ImageView
, adicione uma propriedade app
com o mesmo nome do adaptador de
vinculação que define a imagem. Passe a variável sleep
, conforme mostrado abaixo.sleepImage
é referenciado, o adaptador adaptará os dados de SleepNight
.app:sleepImage="@{sleep}"
sleep_length
e quality_string
. Sempre que
sleepDurationFormatted
ou sleepQualityString
for referenciado, os adaptadores
adaptarão os dados de SleepNight
.app:sleepDurationFormatted="@{sleep}"
app:sleepQualityString="@{sleep}"
ViewHolder
e dando ao código uma estrutura muito melhor do que antes.Você exibiu a mesma lista nos últimos exercícios. Isso é intencional, para mostrar que a interface do
Adapter
permite arquitetar seu código de muitas maneiras diferentes. Quanto mais complexo for o
código, mais importante será arquitetá-lo bem. Em aplicativos de produção, esses padrões e outros são usados
com RecyclerView
. Todos os padrões funcionam e cada um tem seus benefícios. Qual você escolhe
depende do que você está construindo.
Parabéns! Neste ponto, você está no caminho certo para dominar o RecyclerView
no Android.
Projeto Android Studio: RecyclerViewDiffUtilDataBinding.
DiffUtil
:
RecyclerView
tem uma classe chamada DiffUtil
que é para calcular as diferenças
entre duas listas.DiffUtil
tem uma classe chamada ItemCallBack
que você estende para descobrir a
diferença entre duas listas.ItemCallback
, você deve substituir os métodos areItemsTheSame()
e
areContentsTheSame()
. ListAdapter
:
ListAdapter
em
vez de RecyclerView.Adapter
. No entanto, se você usar ListAdapter
, terá que escrever
seu próprio adaptador para outros layouts, e é por isso que este tutorial mostra como fazê-lo.Alt+Enter
(Option+Enter
em um Mac). Este menu é particularmente útil para refatorar
código e criar stubs para implementar métodos. O menu é sensível ao contexto, portanto, você precisa
posicionar o cursor exatamente para obter o menu correto.Vinculação de dados:
Adaptadores de vinculação:
Transformations
para criar strings a partir de dados. Se você precisar vincular
dados de tipos diferentes ou complexos, forneça adaptadores de vinculação para ajudar a vinculação de dados a
usá-los.@BindingAdapter
. No Kotlin, você pode escrever o adaptador de vinculação como uma função de
extensão na View
. Passe o nome da propriedade que o adaptador se adapta. Por exemplo:@BindingAdapter("sleepDurationFormatted")
app
com o mesmo nome do adaptador de vinculação. Passe
uma variável com os dados. Por exemplo:.app:sleepDurationFormatted="@{sleep}"
Documentação para desenvolvimento em Android:
RecyclerView
DiffUtil
notifyDataSetChanged()
Transformations
Outros recursos:
Esta seção lista as possíveis tarefas de casa para os alunos que estão trabalhando neste tutorial como parte de um curso ministrado por um instrutor.
Quais das seguintes opções são necessárias para usar o DiffUtil
? Selecione tudo que se aplica.
▢ Estenda a classe ItemCallBack
.
▢ Substitua areItemsTheSame()
.
▢ Substitua areContentsTheSame()
.
▢ Use vinculação de dados para rastrear as diferenças entre os itens.
Quais das seguintes afirmações são verdadeiras sobre adaptadores de vinculação?
▢ Um adaptador de vinculação é uma função anotada com @BindingAdapter
.
▢ Usando um adaptador de vinculação permite separar a formatação de dados do recipiente de vistas.
▢ Você deve usar um RecyclerViewAdapter
se quiser usar adaptadores de vinculação.
▢ Adaptadores de vinculação são uma boa solução quando você precisa transformar dados complexos.
Quando você deve considerar o uso de Transformations
em vez de um adaptador de vinculação?
Selecione tudo que se aplica.
▢ Seus dados são simples.
▢ Você está formatando uma string.
▢ Sua lista é muito longa.
▢ Seu ViewHolder
contém apenas uma vista.
Comece a próxima lição: