Este codelab faz parte do curso Conceitos básicos do Kotlin para Android. Você aproveitará mais o curso se fizer os codelabs em sequência. Todos os codelabs do curso estão listados na página de destino dos codelabs do curso Conceitos básicos do Kotlin para Android.
Introdução
Este codelab ensina como usar uma RecyclerView
para exibir listas de itens. Com base no app de monitoramento de sono da série anterior de codelabs, você aprenderá uma maneira melhor e mais versátil de exibir dados, usando um RecyclerView
com uma arquitetura recomendada.
O que você já precisa saber
Você precisa:
- Criar uma interface do usuário (IU) básica usando uma atividade, fragmentos e visualizações.
- Navegar entre fragmentos usando
safeArgs
para transmitir dados entre eles. - Usando modelos de visualização, veja fábricas de modelo, transformações e
LiveData
e os observadores. - Criar um banco de dados
Room
, criar um DAO e definir entidades. - Usar corrotinas para tarefas de banco de dados e outras tarefas de longa duração.
O que você vai aprender
- Como usar um
RecyclerView
com umaAdapter
e umViewHolder
para exibir uma lista de itens.
Atividades do laboratório
- Mude o app TrackMySleepQuality da lição anterior para usar um
RecyclerView
para exibir dados de qualidade do sono.
Neste codelab, você criará a parte RecyclerView
de um app que monitora a qualidade do sono. O app usa um banco de dados Room
para armazenar dados de sono ao longo do tempo.
O app inicial do rastreador de sono tem duas telas, representadas por fragmentos, como mostrado na figura abaixo.
A primeira tela, mostrada à esquerda, tem botões para iniciar e interromper o rastreamento. Essa tela também mostra todos os dados de sono do usuário. O botão Limpar exclui permanentemente todos os dados que o app coletou para o usuário. A segunda tela, mostrada à direita, serve para selecionar uma classificação de qualidade do sono.
Esse app usa uma arquitetura simplificada com um controlador de IU, ViewModel
e LiveData
. O app também usa um banco de dados Room
para tornar os dados de sono persistentes.
A lista de noites de sono exibidas na primeira tela é funcional, mas não é bonita. O app usa um formatador complexo para criar strings de texto de visualizações de texto e números para a qualidade. Além disso, o design não é dimensionado. Depois de corrigir todos esses problemas neste codelab, o app final terá a mesma funcionalidade, e a tela principal ficará assim:
A exibição de uma lista ou grade de dados é uma das tarefas mais comuns da IU no Android. As listas variam de simples a muito complexas. Uma lista de visualizações de texto pode mostrar dados simples, como uma lista de compras. Uma lista complexa, como uma lista anotada de destinos de férias, pode mostrar ao usuário muitos detalhes dentro de uma grade de rolagem com cabeçalhos.
Para oferecer compatibilidade com todos esses casos de uso, o Android oferece o widget RecyclerView
.
O maior benefício da RecyclerView
é que ela é muito eficiente para listas grandes:
- Por padrão, o
RecyclerView
só funciona para processar ou desenhar itens que estão visíveis na tela no momento. Por exemplo, se a lista tiver mil elementos, mas apenas 10 estiverem visíveis, aRecyclerView
fará o suficiente para desenhar apenas 10 itens na tela. Quando o usuário rolar a tela, oRecyclerView
descobrirá quais novos itens precisam estar na tela e fará o trabalho suficiente para exibi-los. - Quando um item rola para fora da tela, as visualizações são recicladas. Isso significa que o item é preenchido com um novo conteúdo que aparecerá na tela. Esse comportamento do
RecyclerView
economiza muito tempo de processamento e ajuda as listas a rolarem de forma fluida. - Quando um item é modificado, em vez de redesenhar toda a lista, o
RecyclerView
pode atualizá-lo. Isso é um enorme ganho de eficiência ao exibir listas de itens complexos.
Na sequência mostrada abaixo, é possível ver que uma visualização foi preenchida com dados, ABC
. Depois que essa visualização rola para fora da tela, o RecyclerView
a reutiliza para novos dados, XYZ
.
O padrão do adaptador
Se você viaja entre países que usam diferentes tomadas elétricas, provavelmente sabe como conectar seus dispositivos a uma tomada usando um adaptador. O adaptador permite converter um tipo de conector em outro, o que realmente converte uma interface em outra.
O padrão de adaptador (link em inglês) na engenharia de software ajuda um objeto a trabalhar com outra API. O RecyclerView
usa um adaptador para transformar os dados do app em algo que o RecyclerView
pode exibir, sem mudar a forma como o app armazena e processa os dados. Para o app de rastreador de sono, você cria um adaptador que adapta os dados do banco de dados Room
para algo que o RecyclerView
saiba como exibir, sem mudar as ViewModel
.
Como implementar um RecyclerView
Para exibir seus dados em uma RecyclerView
, você precisa das seguintes partes:
- Dados a serem exibidos.
- Uma instância
RecyclerView
definida no arquivo de layout para atuar como contêiner das visualizações. - Um layout para um item de dados.
Se todos os itens da lista tiverem a mesma aparência, você poderá usar o mesmo layout para todos eles, mas isso não é obrigatório. O layout do item precisa ser criado separadamente do layout do fragmento, de modo que uma visualização de item por vez possa ser criada e preenchida com dados. - Um gerenciador de layout.
O gerenciador de layout gerencia a organização (o layout) dos componentes da IU em uma visualização. - Um armazenador de visualização.
O armazenador de visualização estende a classeViewHolder
. Ele contém as informações de visualização para exibir um item do layout dele. Os armazenadores de visualização também adicionam informações que oRecyclerView
usa para mover as visualizações pela tela de maneira eficiente. - Um adaptador.
O adaptador conecta seus dados aoRecyclerView
. Ela adapta os dados para que possam ser exibidos em umViewHolder
. UmRecyclerView
usa o adaptador para descobrir como exibir os dados na tela.
Nesta tarefa, você adicionará uma RecyclerView
ao arquivo de layout e configurará uma Adapter
para expor dados de sono à RecyclerView
.
Etapa 1: adicionar o RecyclerView com o LayoutManager
Nesta etapa, você substituirá o ScrollView
por um RecyclerView
no arquivo fragment_sleep_tracker.xml
.
- Faça o download do app RecyclerViewFundamentals-Starter no GitHub.
- Crie e execute o app. Observe como os dados são exibidos como texto simples.
- Abra o arquivo de layout
fragment_sleep_tracker.xml
na guia Design no Android Studio. - No painel Component Tree, exclua o
ScrollView
. Essa ação também exclui oTextView
que está dentro doScrollView
. - No painel Palette, role pela lista de tipos de componente à esquerda para encontrar Containers e selecione-a.
- Arraste um
RecyclerView
do painel Palette para o painel Component Tree. Coloque oRecyclerView
dentro doConstraintLayout
.
- Se uma caixa de diálogo aparecer perguntando se você quer adicionar uma dependência, clique em OK para permitir que o Android Studio adicione a dependência
recyclerview
ao arquivo Gradle. Isso pode levar alguns segundos, e o app é sincronizado.
- Abra o arquivo
build.gradle
do módulo, role até o final e anote a nova dependência, que é semelhante ao código abaixo:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
- Volte para o
fragment_sleep_tracker.xml
. - Na guia Text, procure o código
RecyclerView
mostrado abaixo:
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />
- Defina um
id
desleep_list
para aRecyclerView
.
android:id="@+id/sleep_list"
- Posicione o
RecyclerView
para ocupar a parte restante da tela dentro doConstraintLayout
. Para fazer isso, restrinja o topo doRecyclerView
ao botão Start, a parte inferior ao botão Clear e cada lado ao pai. Defina a largura e a altura do layout como 0 dp no editor de layout ou em XML, usando o seguinte código:
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/clear_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stop_button"
- Adicione um gerenciador de layout ao XML
RecyclerView
. CadaRecyclerView
precisa de um gerenciador de layout que informe como posicionar itens na lista. O Android fornece umLinearLayoutManager
, que, por padrão, apresenta os itens em uma lista vertical de linhas de largura total.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- Alterne para a guia Design e observe que as restrições adicionadas fizeram com que o
RecyclerView
se expande para preencher o espaço disponível.
Etapa 2: criar o layout do item da lista e o armazenador de visualização de texto
O RecyclerView
é apenas um contêiner. Nesta etapa, você criará o layout e a infraestrutura para os itens que serão exibidos no RecyclerView
.
Para chegar a um RecyclerView
funcional o mais rápido possível, primeiro use um item da lista simplista que exiba apenas a qualidade do sono em número. Para isso, você precisa de um fixador de visualização, TextItemViewHolder
. Também é necessário ter uma visualização, um TextView
, para os dados. Em uma etapa posterior, você aprenderá mais sobre os armazenadores de visualização e como dispor todos os dados de sono.
- Crie um arquivo de layout chamado
text_item_view.xml
. Ele não importa o que você usa como elemento raiz, porque o código do modelo será substituído. - Em
text_item_view.xml
, exclua todo o código fornecido. - Adicione uma
TextView
com padding16dp
no início e no fim e um tamanho de texto de24sp
. Deixe a largura corresponder à mãe e a altura encapsula o conteúdo. Como essa visualização é exibida noRecyclerView
, não é necessário colocá-la em umViewGroup
.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:textSize="24sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
- Abra o
Util.kt
Role para o fim e adicione a definição mostrada abaixo, que cria a classeTextItemViewHolder
. Coloque o código na parte inferior do arquivo, depois da última chave de fechamento. O código fica emUtil.kt
porque esse armazenador de visualização é temporário e você o substituirá mais tarde.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
- Se solicitado, importe
android.widget.TextView
eandroidx.recyclerview.widget.RecyclerView
.
Etapa 3: criar SleepNightAdapter
A tarefa principal ao implementar um RecyclerView
é criar o adaptador. Você tem um armazenador de visualização simples para a visualização de item e um layout para cada item. Agora você pode criar um adaptador. O adaptador cria um armazenador de visualização e o preenche com dados para a exibição de RecyclerView
.
- No pacote
sleeptracker
, crie uma nova classe do Kotlin com o nomeSleepNightAdapter
. - Faça a classe
SleepNightAdapter
estenderRecyclerView.Adapter
. A classe é chamadaSleepNightAdapter
porque adapta um objetoSleepNight
em algo que aRecyclerView
pode usar. O adaptador precisa saber qual armazenador de visualização usar. Portanto, transmitaTextItemViewHolder
. Importe os componentes necessários quando solicitado. Caso contrário, você verá um erro, porque existem métodos obrigatórios para implementar.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
- No nível superior do
SleepNightAdapter
, crie uma variávelSleepNight
dolistOf
para armazenar os dados.
var data = listOf<SleepNight>()
- Em
SleepNightAdapter
, substituagetItemCount()
para retornar o tamanho da lista de noites de sono emdata
. ORecyclerView
precisa saber quantos itens o adaptador tem para exibir e faz isso chamandogetItemCount()
.
override fun getItemCount() = data.size
- Em
SleepNightAdapter
, substitua a funçãoonBindViewHolder()
, conforme mostrado abaixo.
A funçãoonBindViewHolder()
é chamada peloRecyclerView
para exibir os dados de um item da lista na posição especificada. Assim, o métodoonBindViewHolder()
usa dois argumentos: um armazenador de visualização e uma posição dos dados que serão vinculados. Para este app, o armazenador é aTextItemViewHolder
, que é a posição na lista.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
- No
onBindViewHolder()
, crie uma variável para um item em uma determinada posição nos dados.
val item = data[position]
- O
ViewHolder
que você criou tem uma propriedade chamadatextView
. Dentro deonBindViewHolder()
, definatext
detextView
como o número da qualidade do sono. Esse código exibe apenas uma lista de números, mas este exemplo simples permite ver como o adaptador coloca os dados no armazenador de visualização e na tela.
holder.textView.text = item.sleepQuality.toString()
- Em
SleepNightAdapter
, substitua e implementeonCreateViewHolder()
, que é chamado quando oRecyclerView
precisa que um armazenador de visualização represente um item.
Essa função usa dois parâmetros e retorna umViewHolder
. O parâmetroparent
, que é o grupo de visualização que contém o fixador de visualização, é sempre oRecyclerView
. O parâmetroviewType
é usado quando há várias visualizações no mesmoRecyclerView
. Por exemplo, se você inserir uma lista de visualizações de texto, uma imagem e um vídeo no mesmoRecyclerView
, a funçãoonCreateViewHolder()
precisará saber que tipo de visualização usar.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
- Em
onCreateViewHolder()
, crie uma instância deLayoutInflater
.
O inflador de layout sabe criar visualizações usando layouts XML. Ocontext
contém informações sobre como inflar a visualização corretamente. Em um adaptador para uma visualização de reciclagem, você sempre transmite o contexto do grupo de visualizaçõesparent
, que é oRecyclerView
.
val layoutInflater = LayoutInflater.from(parent.context)
- Em
onCreateViewHolder()
, crie aview
solicitando que alayoutinflater
a infle.
Transmita o layout XML da visualização e o grupo de visualizaçõesparent
para ela. O terceiro, booleano, argumento éattachToRoot
. Esse argumento precisa serfalse
, porque oRecyclerView
adicionará esse item à hierarquia de visualização no momento certo.
val view = layoutInflater
.inflate(R.layout.text_item_view, parent, false) as TextView
- Em
onCreateViewHolder()
, retorne umaTextItemViewHolder
feita comview
.
return TextItemViewHolder(view)
- O adaptador precisa informar ao
RecyclerView
quando odata
mudou, porque oRecyclerView
não sabe nada sobre os dados. Ela sabe apenas sobre os armazenadores de visualização que o adaptador oferece a ela.
Para informar aRecyclerView
quando os dados que ela está exibindo tiverem mudado, adicione um setter personalizado à variáveldata
que está na parte superior da classeSleepNightAdapter
. No setter, atribua um novo valor adata
e chamenotifyDataSetChanged()
para acionar o novo desenho da lista com os novos dados.
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}
Etapa 4: informar o RecyclerView ao RecyclerView
O RecyclerView
precisa saber sobre o adaptador a ser usado para acessar os armazenadores de visualização.
- Abra o
SleepTrackerFragment.kt
- Em
onCreateview()
, crie um adaptador. Coloque esse código após a criação do modeloViewModel
e antes da instruçãoreturn
.
val adapter = SleepNightAdapter()
- Associe o
adapter
aoRecyclerView
.
binding.sleepList.adapter = adapter
- Limpe e recrie seu projeto para atualizar o objeto
binding
.
Se ainda houver erros embinding.sleepList
oubinding.FragmentSleepTrackerBinding
, invalide os caches e faça a reinicialização. Selecione File > Invalidate Caches / Restart.
Se você executar o app agora, não haverá erros, mas os dados não serão exibidos quando você tocar em Iniciar e em Parar.
Etapa 5: transferir dados para o adaptador
Até agora, você tem um adaptador e uma maneira de transferir dados do adaptador para o RecyclerView
. Agora você precisa colocar dados no adaptador da ViewModel
.
- Abra o
SleepTrackerViewModel
- Localize a variável
nights
, que armazena todas as noites de sono, que são os dados a serem exibidos. A variávelnights
é definida chamandogetAllNights()
no banco de dados. - Remova
private
denights
, porque você criará um observador que precisa acessar essa variável. A declaração ficará assim:
val nights = database.getAllNights()
- No pacote
database
, abra aSleepDatabaseDao
. - Localize a função
getAllNights()
. Essa função retorna uma lista de valoresSleepNight
comoLiveData
. Isso significa que a variávelnights
contémLiveData
, que é atualizado porRoom
. Você pode observarnights
para saber quando ele muda. - Abra o
SleepTrackerFragment
- Em
onCreateView()
, abaixo da criação deadapter
, crie um observador na variávelnights
.
Ao fornecer asviewLifecycleOwner
do fragmento como proprietário do ciclo de vida, você pode garantir que esse observador esteja ativo somente quando aRecyclerView
estiver na tela.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
})
- Dentro do observador, sempre que você receber um valor não nulo (para
nights
), atribua o valor aodata
do adaptador. Este é o código completo do observador e a configuração dos dados:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})
- Crie e execute seu código.
Se o adaptador estiver funcionando, você verá os números de qualidade do sono em uma lista. A captura de tela à esquerda mostra -1 depois que você toca em Iniciar. A captura de tela à direita mostra o número atualizado da qualidade do sono depois que você toca em Parar e seleciona uma classificação de qualidade.
Etapa 6: explorar como os fixadores de visualização são reciclados
RecyclerView
recicla os armazenadores de visualização, o que significa que os reutiliza. À medida que uma visualização rola para fora da tela, o RecyclerView
a reutiliza para a visualização que está prestes a ser rolada na tela.
Como esses armazenadores de visualização são reciclados, verifique se o onBindViewHolder()
define ou redefine todas as personalizações que os itens anteriores podem ter definido em um armazenador de visualização.
Por exemplo, você pode definir a cor do texto para vermelho nos fixadores de visualização que têm classificações de qualidade menores ou iguais a 1 e representam sono inadequado.
- Na classe
SleepNightAdapter
, adicione o seguinte código no fim do métodoonBindViewHolder()
.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
}
- Execute o app.
- Adicione alguns dados de baixa qualidade para dormir, e o número ficará vermelho.
- Adicione notas altas para a qualidade do sono até ver um número alto vermelho na tela.
Ao reutilizar os armazenadores de visualização, oRecyclerView
reutiliza um deles para melhorar a qualidade. A classificação alta é exibida por engano em vermelho.
- Para corrigir isso, adicione uma instrução
else
para definir a cor como preto se a qualidade não for menor ou igual a um.
Com as duas condições explícitas, o armazenador de visualização usará a cor de texto correta para cada item.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
} else {
// reset
holder.textView.setTextColor(Color.BLACK) // black
}
- Execute o app. Os números sempre terão a cor correta.
Parabéns! Agora você tem um RecyclerView
básico totalmente funcional.
Nesta tarefa, você substituirá o armazenador de visualização simples por um que possa exibir mais dados para uma noite de sono.
O ViewHolder
simples que você adicionou a Util.kt
apenas envolve um TextView
em um TextItemViewHolder
.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
Então por que o RecyclerView
não usa apenas um TextView
diretamente? Essa única linha de código oferece muitas funcionalidades. Um ViewHolder
descreve uma visualização de itens e os metadados sobre o lugar deles no RecyclerView
. O RecyclerView
depende dessa funcionalidade para posicionar corretamente a visualização à medida que a lista rola e para fazer coisas interessantes, como visualizações animadas quando itens são adicionados ou removidos no Adapter
.
Se RecyclerView
precisar acessar as visualizações armazenadas no ViewHolder
, ele poderá fazer isso usando a propriedade itemView
do fixador de visualização. O RecyclerView
usa itemView
quando está vinculando um item para exibição na tela, ao desenhar decorações ao redor de uma visualização, como uma borda e para implementar a acessibilidade.
Etapa 1: criar o layout do item
Nesta etapa, você criará o arquivo de layout para um item. O layout consiste em um ConstraintLayout
com uma ImageView
para a qualidade do sono, um TextView
para a duração do sono e um TextView
para a qualidade como texto. Como você já fez os layouts, copie e cole o código XML fornecido.
- Crie um novo arquivo de recursos de layout e nomeie-o como
list_item_sleep_night
. - Substitua todo o código do arquivo pelo código abaixo. Depois, familiarize-se com o layout que você acabou de criar.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/quality_image"
android:layout_width="@dimen/icon_size"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_sleep_5" />
<TextView
android:id="@+id/sleep_length"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/quality_image"
app:layout_constraintTop_toTopOf="@+id/quality_image"
tools:text="Wednesday" />
<TextView
android:id="@+id/quality_string"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="@+id/sleep_length"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/sleep_length"
app:layout_constraintTop_toBottomOf="@+id/sleep_length"
tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>
- Alterne para a guia Design no Android Studio. Na Visualização de design, o layout será parecido com a captura de tela à esquerda abaixo. Na visualização de blueprints, ele será semelhante à captura de tela à direita.
Etapa 2: criar um ViewHolder
- Abra o
SleepNightAdapter.kt
- Crie uma classe dentro do
SleepNightAdapter
com o nomeViewHolder
e estenda aRecyclerView.ViewHolder
.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
- Dentro de
ViewHolder
, acesse referências às visualizações. Você precisa de uma referência às visualizações queViewHolder
atualizará. Sempre que você vincular esteViewHolder
, precisará acessar a imagem e as duas visualizações de texto. Converta esse código para usar a vinculação de dados mais tarde.
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)
Etapa 3: usar o ViewHolder em SleepNightAdapter
- Na definição do
SleepNightAdapter
, use oSleepNightAdapter.ViewHolder
que você acabou de criar em vez doTextItemViewHolder
.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {
Atualize onCreateViewHolder()
:
- Mude a assinatura de
onCreateViewHolder()
para retornar oViewHolder
. - Mude o inflador de layout para usar o recurso de layout correto,
list_item_sleep_night
. - Remova o elenco para
TextView
. - Em vez de retornar um
TextItemViewHolder
, retorne umViewHolder
.
Esta é a funçãoonCreateViewHolder()
atualizada:
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater =
LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night,
parent, false)
return ViewHolder(view)
}
Atualize onBindViewHolder()
:
- Mude a assinatura da
onBindViewHolder()
para que o parâmetroholder
seja umViewHolder
em vez de umTextItemViewHolder
. - Dentro de
onBindViewHolder()
, exclua todo o código, exceto a definição deitem
. - Defina uma
res
doval
que contenha uma referência àresources
da visualização.
val res = holder.itemView.context.resources
- Defina a duração do texto da visualização de texto do
sleepLength
. Copie o código abaixo, que chama uma função de formatação fornecida com o código inicial.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
- Isso gera um erro, porque
convertDurationToFormatted()
precisa ser definido. AbraUtil.kt
e remova a marca de comentário do código e as importações associadas. Selecione Code > Comment with Line comments. - Em
onBindViewHolder()
, useconvertNumericQualityToString()
para definir a qualidade.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
- Talvez seja necessário importar manualmente essas funções.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
- Defina o ícone correto para a qualidade. O novo ícone
ic_sleep_active
é fornecido no código inicial.
holder.qualityImage.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
})
- Esta é a função
onBindViewHolder()
atualizada, que define todos os dados daViewHolder
:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.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
})
}
- Execute o app. A tela será parecida com a captura de tela abaixo, que mostra o ícone da qualidade do sono, além de texto sobre a duração e a qualidade do sono.
O RecyclerView
foi concluído. Você aprendeu a implementar um Adapter
e um ViewHolder
e os reuniu para exibir uma lista com um RecyclerView
Adapter
.
Até o momento, seu código mostra o processo de criação de um adaptador e de um armazenador de visualização. No entanto, você pode melhorar esse código. O código a ser exibido e o código para gerenciar os armazenadores de visualização são misturados, e o onBindViewHolder()
sabe detalhes sobre como atualizar o ViewHolder
.
Em um app de produção, você pode ter vários armazenadores de visualização, adaptadores mais complexos e vários desenvolvedores fazendo alterações. Estruturar o código de forma que tudo o que estiver relacionado a um armazenador de visualização esteja somente nesse armazenador.
Etapa 1: refatorar onBindViewHolder()
Nesta etapa, você refatorará o código e moverá todas as funcionalidades do armazenador de visualização para o ViewHolder
. O objetivo dessa refatoração não é mudar a aparência do app para o usuário, mas tornar mais fácil e segura para os desenvolvedores trabalharem no código. Felizmente, o Android Studio tem ferramentas para ajudar.
- Em
SleepNightAdapter
, emonBindViewHolder()
, selecione tudo, exceto a instrução para declarar a variávelitem
. - Clique com o botão direito do mouse e selecione Refactor > Extract > Function.
- Nomeie a função
bind
e aceite os parâmetros sugeridos. Clique em OK.
A funçãobind()
é colocada abaixo deonBindViewHolder()
.
private fun bind(holder: ViewHolder, item: SleepNight) {
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.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
})
}
- Coloque o cursor sobre a palavra
holder
do parâmetroholder
debind()
. PressioneAlt+Enter
(Option+Enter
em um Mac) para abrir o menu de intents. Selecione Converter parâmetro em receptor para converter isso em uma função de extensão que tenha a seguinte assinatura:
private fun ViewHolder.bind(item: SleepNight) {...}
- Recorte e cole a função
bind()
noViewHolder
. - Tornar
bind()
público. - Importe
bind()
para o adaptador, se necessário. - Como agora ele está no
ViewHolder
, você pode remover a parteViewHolder
da assinatura. Este é o código final da funçãobind()
na classeViewHolder
.
fun bind(item: SleepNight) {
val res = itemView.context.resources
sleepLength.text = convertDurationToFormatted(
item.startTimeMilli, item.endTimeMilli, res)
quality.text = convertNumericQualityToString(
item.sleepQuality, res)
qualityImage.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
})
}
Etapa 2: refatorar onCreateViewHolder
No momento, o método onCreateViewHolder()
no adaptador infla a visualização do recurso de layout para a ViewHolder
. No entanto, a inflação não tem nada a ver com o adaptador e tudo a ver com o ViewHolder
. A inflação deve acontecer no ViewHolder
.
- Em
onCreateViewHolder()
, selecione todo o código no corpo da função. - Clique com o botão direito do mouse e selecione Refactor > Extract > Function.
- Nomeie a função
from
e aceite os parâmetros sugeridos. Clique em OK. - Coloque o cursor sobre o nome da função
from
. PressioneAlt+Enter
(Option+Enter
em um Mac) para abrir o menu de intents. - Selecione Mover para o objeto complementar. A função
from()
precisa estar em um objeto complementar para que ela possa ser chamada na classeViewHolder
, não em uma instância doViewHolder
. - Mova o objeto
companion
para a classeViewHolder
. - Tornar
from()
público. - Em
onCreateViewHolder()
, mude a instruçãoreturn
para retornar o resultado da chamada defrom()
na classeViewHolder
.
Os métodosonCreateViewHolder()
efrom()
concluídos ficarão semelhantes ao código abaixo, e ele será criado e executado sem erros.
override fun onCreateViewHolder(parent: ViewGroup, viewType:
Int): ViewHolder {
return ViewHolder.from(parent)
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night, parent, false)
return ViewHolder(view)
}
}
- Mude a assinatura da classe
ViewHolder
para que o construtor seja particular. Comofrom()
agora é um método que retorna uma nova instância deViewHolder
, não há motivo para ninguém chamar o construtor deViewHolder
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
- Execute o app. Ele será criado e executado da mesma forma que antes, que é o resultado desejado após a refatoração.
Projeto do Android Studio: RecyclerViewFundamentals
- A exibição de uma lista ou grade de dados é uma das tarefas mais comuns da IU no Android. O
RecyclerView
foi projetado para ser eficiente, mesmo ao exibir listas extremamente grandes. - O
RecyclerView
só faz o trabalho necessário para processar ou desenhar itens que estão visíveis na tela no momento. - Quando um item rola para fora da tela, as visualizações são recicladas. Isso significa que o item é preenchido com um novo conteúdo que aparecerá na tela.
- O padrão de adaptador (link em inglês) na engenharia de software ajuda um objeto a trabalhar em conjunto com outra API. O
RecyclerView
usa um adaptador para transformar os dados do app em algo que ele pode exibir, sem precisar mudar a forma como o app armazena e processa dados.
Para exibir seus dados em uma RecyclerView
, você precisa das seguintes partes:
- RecyclerView
: para criar uma instância deRecyclerView
, defina um elemento<RecyclerView>
no arquivo de layout. - LayoutManager
UmaRecyclerView
usa umaLayoutManager
para organizar o layout dos itens naRecyclerView
, por exemplo, colocando-os em uma grade ou em uma lista linear.
Na<RecyclerView>
no arquivo de layout, defina o atributoapp:layoutManager
para o gerenciador de layout (comoLinearLayoutManager
ouGridLayoutManager
).
Também é possível definir aLayoutManager
para umRecyclerView
de forma programática. Essa técnica será abordada em um outro codelab. - Layout para cada item
Crie um layout para um item de dados em um arquivo de layout XML. - Adaptador
: crie um adaptador que prepare os dados e como eles serão exibidos em umViewHolder
. Associe o adaptador aoRecyclerView
.
QuandoRecyclerView
é executado, ele usa o adaptador para descobrir como exibir os dados na tela.
O adaptador exige que você implemente os seguintes métodos:
–getItemCount()
para retornar o número de itens.
–onCreateViewHolder()
para retornar oViewHolder
de um item na lista.
–onBindViewHolder()
para adaptar os dados às visualizações de um item na lista. - ViewHolder
UmViewHolder
contém as informações de visualização para exibir um item do layout do item. - O método
onBindViewHolder()
no adaptador adapta os dados para as visualizações. Você sempre substitui esse método. Normalmente, aonBindViewHolder()
infla o layout de um item e coloca os dados nas visualizações do layout. - Como o
RecyclerView
não sabe nada sobre os dados, oAdapter
precisa informar oRecyclerView
quando esses dados mudarem. UsenotifyDataSetChanged()
para notificarAdapter
sobre a mudança nos dados.
Curso da Udacity:
- Como desenvolver apps Android com Kotlin (link em inglês)
Documentação do desenvolvedor Android:
Esta seção lista as possíveis atividades para os alunos que estão trabalhando neste codelab como parte de um curso ministrado por um instrutor. Cabe ao instrutor fazer o seguinte:
- Se necessário, atribua o dever de casa.
- Informe aos alunos como enviar o dever de casa.
- Atribua nota aos trabalhos de casa.
Os professores podem usar essas sugestões o quanto quiserem, e eles devem se sentir à vontade para passar o dever de casa como achar adequado.
Se você estiver fazendo este codelab por conta própria, use essas atividades para testar seu conhecimento.
Responda a estas perguntas
Pergunta 1
Como o RecyclerView
exibe itens? Selecione todas as opções aplicáveis.
▢ Exibe itens em uma lista ou grade.
▢ Rola vertical ou horizontalmente.
▢ Rola diagonalmente em dispositivos maiores, como tablets.
▢ Permite layouts personalizados quando uma lista ou grade não é o suficiente para o caso de uso.
Pergunta 2
Quais são os benefícios de usar o RecyclerView
? Selecione todas as opções aplicáveis.
▢ Exibe listas grandes com eficiência.
▢ Atualiza os dados automaticamente.
▢ Minimiza a necessidade de atualizações quando itens são atualizados, excluídos ou adicionados à lista.
▢ Reutiliza a visualização que rola para fora da tela, exibindo o próximo item que rola na tela.
Pergunta 3
Quais são alguns dos motivos para o uso de adaptadores? Selecione todas as opções aplicáveis.
▢ Com a separação de problemas, é mais fácil mudar e testar o código.
▢ RecyclerView
é independente do conjunto de dados exibido.
▢ As camadas de processamento de dados não precisam se preocupar com como os dados serão exibidos.
▢ O app vai funcionar mais rápido.
Pergunta 4
Quais das opções a seguir são verdadeiras sobre ViewHolder
? Selecione todas as opções aplicáveis.
▢ O layout ViewHolder
é definido em arquivos de layout XML.
▢ Há uma ViewHolder
para cada unidade de dados no conjunto.
▢ Você pode ter mais de um ViewHolder
em uma RecyclerView
.
▢ O Adapter
vincula dados ao ViewHolder
.
Inicie a próxima lição: