Conceitos básicos do Kotlin para Android 07.1: noções básicas do RecyclerView

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 uma Adapter e um ViewHolder 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, a RecyclerView fará o suficiente para desenhar apenas 10 itens na tela. Quando o usuário rolar a tela, o RecyclerView 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 classe ViewHolder. 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 o RecyclerView usa para mover as visualizações pela tela de maneira eficiente.
  • Um adaptador.
    O adaptador conecta seus dados ao RecyclerView. Ela adapta os dados para que possam ser exibidos em um ViewHolder. Um RecyclerView 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.

  1. Faça o download do app RecyclerViewFundamentals-Starter no GitHub.
  2. Crie e execute o app. Observe como os dados são exibidos como texto simples.
  3. Abra o arquivo de layout fragment_sleep_tracker.xml na guia Design no Android Studio.
  4. No painel Component Tree, exclua o ScrollView. Essa ação também exclui o TextView que está dentro do ScrollView.
  5. No painel Palette, role pela lista de tipos de componente à esquerda para encontrar Containers e selecione-a.
  6. Arraste um RecyclerView do painel Palette para o painel Component Tree. Coloque o RecyclerView dentro do ConstraintLayout.

  1. 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.

  1. 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'
  1. Volte para o fragment_sleep_tracker.xml.
  2. Na guia Text, procure o código RecyclerView mostrado abaixo:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Defina um id de sleep_list para a RecyclerView.
android:id="@+id/sleep_list"
  1. Posicione o RecyclerView para ocupar a parte restante da tela dentro do ConstraintLayout. Para fazer isso, restrinja o topo do RecyclerView 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"
  1. Adicione um gerenciador de layout ao XML RecyclerView. Cada RecyclerView precisa de um gerenciador de layout que informe como posicionar itens na lista. O Android fornece um LinearLayoutManager, que, por padrão, apresenta os itens em uma lista vertical de linhas de largura total.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. 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.

  1. 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.
  2. Em text_item_view.xml, exclua todo o código fornecido.
  3. Adicione uma TextView com padding 16dp no início e no fim e um tamanho de texto de 24sp. Deixe a largura corresponder à mãe e a altura encapsula o conteúdo. Como essa visualização é exibida no RecyclerView, não é necessário colocá-la em um ViewGroup.
<?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" />
  1. Abra o Util.kt Role para o fim e adicione a definição mostrada abaixo, que cria a classe TextItemViewHolder. Coloque o código na parte inferior do arquivo, depois da última chave de fechamento. O código fica em Util.kt porque esse armazenador de visualização é temporário e você o substituirá mais tarde.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. Se solicitado, importe android.widget.TextView e androidx.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.

  1. No pacote sleeptracker, crie uma nova classe do Kotlin com o nome SleepNightAdapter.
  2. Faça a classe SleepNightAdapter estender RecyclerView.Adapter. A classe é chamada SleepNightAdapter porque adapta um objeto SleepNight em algo que a RecyclerView pode usar. O adaptador precisa saber qual armazenador de visualização usar. Portanto, transmita TextItemViewHolder. 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>() {}
  1. No nível superior do SleepNightAdapter, crie uma variável SleepNight do listOf para armazenar os dados.
var data =  listOf<SleepNight>()
  1. Em SleepNightAdapter, substitua getItemCount() para retornar o tamanho da lista de noites de sono em data. O RecyclerView precisa saber quantos itens o adaptador tem para exibir e faz isso chamando getItemCount().
override fun getItemCount() = data.size
  1. Em SleepNightAdapter, substitua a função onBindViewHolder(), conforme mostrado abaixo.

    A função onBindViewHolder() é chamada pelo RecyclerView para exibir os dados de um item da lista na posição especificada. Assim, o método onBindViewHolder() usa dois argumentos: um armazenador de visualização e uma posição dos dados que serão vinculados. Para este app, o armazenador é a TextItemViewHolder, que é a posição na lista.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. No onBindViewHolder(), crie uma variável para um item em uma determinada posição nos dados.
 val item = data[position]
  1. O ViewHolder que você criou tem uma propriedade chamada textView. Dentro de onBindViewHolder(), defina text de textView 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()
  1. Em SleepNightAdapter, substitua e implemente onCreateViewHolder(), que é chamado quando o RecyclerView precisa que um armazenador de visualização represente um item.

    Essa função usa dois parâmetros e retorna um ViewHolder. O parâmetro parent, que é o grupo de visualização que contém o fixador de visualização, é sempre o RecyclerView. O parâmetro viewType é usado quando há várias visualizações no mesmo RecyclerView. Por exemplo, se você inserir uma lista de visualizações de texto, uma imagem e um vídeo no mesmo RecyclerView, a função onCreateViewHolder() precisará saber que tipo de visualização usar.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
  1. Em onCreateViewHolder(), crie uma instância de LayoutInflater.

    O inflador de layout sabe criar visualizações usando layouts XML. O context 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ções parent, que é o RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. Em onCreateViewHolder(), crie a view solicitando que a layoutinflater a infle.

    Transmita o layout XML da visualização e o grupo de visualizações parent para ela. O terceiro, booleano, argumento é attachToRoot. Esse argumento precisa ser false, porque o RecyclerView adicionará esse item à hierarquia de visualização no momento certo.
val view = layoutInflater
       .inflate(R.layout.text_item_view, parent, false) as TextView
  1. Em onCreateViewHolder(), retorne uma TextItemViewHolder feita com view.
return TextItemViewHolder(view)
  1. O adaptador precisa informar ao RecyclerView quando o data mudou, porque o RecyclerView não sabe nada sobre os dados. Ela sabe apenas sobre os armazenadores de visualização que o adaptador oferece a ela.

    Para informar a RecyclerView quando os dados que ela está exibindo tiverem mudado, adicione um setter personalizado à variável data que está na parte superior da classe SleepNightAdapter. No setter, atribua um novo valor a data e chame notifyDataSetChanged() 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.

  1. Abra o SleepTrackerFragment.kt
  2. Em onCreateview(), crie um adaptador. Coloque esse código após a criação do modelo ViewModel e antes da instrução return.
val adapter = SleepNightAdapter()
  1. Associe o adapter ao RecyclerView.
binding.sleepList.adapter = adapter
  1. Limpe e recrie seu projeto para atualizar o objeto binding.

    Se ainda houver erros em binding.sleepList ou binding.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.

  1. Abra o SleepTrackerViewModel
  2. Localize a variável nights, que armazena todas as noites de sono, que são os dados a serem exibidos. A variável nights é definida chamando getAllNights() no banco de dados.
  3. Remova private de nights, porque você criará um observador que precisa acessar essa variável. A declaração ficará assim:
val nights = database.getAllNights()
  1. No pacote database, abra a SleepDatabaseDao.
  2. Localize a função getAllNights(). Essa função retorna uma lista de valores SleepNight como LiveData. Isso significa que a variável nights contém LiveData, que é atualizado por Room. Você pode observar nights para saber quando ele muda.
  3. Abra o SleepTrackerFragment
  4. Em onCreateView(), abaixo da criação de adapter, crie um observador na variável nights.

    Ao fornecer as viewLifecycleOwner do fragmento como proprietário do ciclo de vida, você pode garantir que esse observador esteja ativo somente quando a RecyclerView estiver na tela.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. Dentro do observador, sempre que você receber um valor não nulo (para nights), atribua o valor ao data 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
   }
})
  1. 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.

  1. Na classe SleepNightAdapter, adicione o seguinte código no fim do método onBindViewHolder().
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. Execute o app.
  2. Adicione alguns dados de baixa qualidade para dormir, e o número ficará vermelho.
  3. Adicione notas altas para a qualidade do sono até ver um número alto vermelho na tela.

    Ao reutilizar os armazenadores de visualização, o RecyclerView reutiliza um deles para melhorar a qualidade. A classificação alta é exibida por engano em vermelho.

  1. 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
}
  1. 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.

  1. Crie um novo arquivo de recursos de layout e nomeie-o como list_item_sleep_night.
  2. 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>
  1. 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

  1. Abra o SleepNightAdapter.kt
  2. Crie uma classe dentro do SleepNightAdapter com o nome ViewHolder e estenda a RecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. Dentro de ViewHolder, acesse referências às visualizações. Você precisa de uma referência às visualizações que ViewHolder atualizará. Sempre que você vincular este ViewHolder, 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

  1. Na definição do SleepNightAdapter, use o SleepNightAdapter.ViewHolder que você acabou de criar em vez do TextItemViewHolder.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

Atualize onCreateViewHolder():

  1. Mude a assinatura de onCreateViewHolder() para retornar o ViewHolder.
  2. Mude o inflador de layout para usar o recurso de layout correto, list_item_sleep_night.
  3. Remova o elenco para TextView.
  4. Em vez de retornar um TextItemViewHolder, retorne um ViewHolder.

    Esta é a função onCreateViewHolder() 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():

  1. Mude a assinatura da onBindViewHolder() para que o parâmetro holder seja um ViewHolder em vez de um TextItemViewHolder.
  2. Dentro de onBindViewHolder(), exclua todo o código, exceto a definição de item.
  3. Defina uma res do val que contenha uma referência à resources da visualização.
val res = holder.itemView.context.resources
  1. 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)
  1. Isso gera um erro, porque convertDurationToFormatted() precisa ser definido. Abra Util.kt e remova a marca de comentário do código e as importações associadas. Selecione Code > Comment with Line comments.
  2. Em onBindViewHolder(), use convertNumericQualityToString() para definir a qualidade.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. Talvez seja necessário importar manualmente essas funções.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
  1. 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
})
  1. Esta é a função onBindViewHolder() atualizada, que define todos os dados da ViewHolder:
   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
        })
    }
  1. 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.

  1. Em SleepNightAdapter, em onBindViewHolder(), selecione tudo, exceto a instrução para declarar a variável item.
  2. Clique com o botão direito do mouse e selecione Refactor > Extract > Function.
  3. Nomeie a função bind e aceite os parâmetros sugeridos. Clique em OK.

    A função bind() é colocada abaixo de onBindViewHolder().
    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
        })
    }
  1. Coloque o cursor sobre a palavra holder do parâmetro holder de bind(). Pressione Alt+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) {...}
  1. Recorte e cole a função bind() no ViewHolder.
  2. Tornar bind() público.
  3. Importe bind() para o adaptador, se necessário.
  4. Como agora ele está no ViewHolder, você pode remover a parte ViewHolder da assinatura. Este é o código final da função bind() na classe ViewHolder.
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.

  1. Em onCreateViewHolder(), selecione todo o código no corpo da função.
  2. Clique com o botão direito do mouse e selecione Refactor > Extract > Function.
  3. Nomeie a função from e aceite os parâmetros sugeridos. Clique em OK.
  4. Coloque o cursor sobre o nome da função from. Pressione Alt+Enter (Option+Enter em um Mac) para abrir o menu de intents.
  5. Selecione Mover para o objeto complementar. A função from() precisa estar em um objeto complementar para que ela possa ser chamada na classe ViewHolder, não em uma instância do ViewHolder.
  6. Mova o objeto companion para a classe ViewHolder.
  7. Tornar from() público.
  8. Em onCreateViewHolder(), mude a instrução return para retornar o resultado da chamada de from() na classe ViewHolder.

    Os métodos onCreateViewHolder() e from() 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)
   }
}
  1. Mude a assinatura da classe ViewHolder para que o construtor seja particular. Como from() agora é um método que retorna uma nova instância de ViewHolder, não há motivo para ninguém chamar o construtor de ViewHolder
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. 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 de RecyclerView, defina um elemento <RecyclerView> no arquivo de layout.
  • LayoutManager
    Uma RecyclerView usa uma LayoutManager para organizar o layout dos itens na RecyclerView, por exemplo, colocando-os em uma grade ou em uma lista linear.

    Na <RecyclerView> no arquivo de layout, defina o atributo app:layoutManager para o gerenciador de layout (como LinearLayoutManager ou GridLayoutManager).

    Também é possível definir a LayoutManager para um RecyclerView 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 um ViewHolder. Associe o adaptador ao RecyclerView.

    Quando RecyclerView é 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 o ViewHolder de um item na lista.
    onBindViewHolder() para adaptar os dados às visualizações de um item na lista.

  • ViewHolder
    Um ViewHolder 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, a onBindViewHolder() 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, o Adapter precisa informar o RecyclerView quando esses dados mudarem. Use notifyDataSetChanged() para notificar Adapter sobre a mudança nos dados.

Curso da Udacity:

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: 7.2: DiffUtil e vinculação de dados com o RecyclerView.