Conceitos básicos do Kotlin para Android 07.1: conceitos básicos da RecyclerView

Este codelab faz parte do curso Conceitos básicos do Kotlin para Android. Você vai aproveitar mais este curso se fizer os codelabs em sequência. Todos os codelabs do curso estão listados na página inicial dos codelabs de princípios básicos do Kotlin para Android.

Introdução

Este codelab ensina a usar um RecyclerView para mostrar listas de itens. Com base no app de rastreamento do sono da série anterior de codelabs, você vai aprender uma maneira melhor e mais versátil de mostrar dados usando um RecyclerView com uma arquitetura recomendada.

O que você já precisa saber

Você precisa:

  • Criar uma interface do usuário (UI) básica usando uma atividade, fragmentos e visualizações.
  • Navegar entre fragmentos e usar safeArgs para transmitir dados entre eles.
  • Usando ViewModels, fábricas de ViewModels, transformações, LiveData e os observadores deles.
  • Criar um banco de dados Room, 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 um Adapter e um ViewHolder para mostrar uma lista de itens.

Atividades deste laboratório

  • Mude o app TrackMySleepQuality da lição anterior para usar um RecyclerView e mostrar os dados de qualidade do sono.

Neste codelab, você vai 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 rastreador de sono inicial tem duas telas, representadas por fragmentos, conforme mostrado na figura abaixo.

A primeira tela, mostrada à esquerda, tem botões para iniciar e parar 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 do usuário. A segunda tela, mostrada à direita, é para selecionar uma classificação de qualidade do sono.

Esse app usa uma arquitetura simplificada com um controlador de UI, 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 mostrada na primeira tela é funcional, mas não é bonita. O app usa um formatador complexo para criar strings de texto para a visualização de texto e números para a qualidade. Além disso, esse design não é escalonável. Depois de corrigir todos esses problemas neste codelab, o app final terá a mesma funcionalidade, e a tela principal vai ficar assim:

Mostrar uma lista ou grade de dados é uma das tarefas mais comuns da interface do usuário 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 em uma grade rolável com cabeçalhos.

Para oferecer suporte a todos esses casos de uso, o Android fornece o widget RecyclerView.

O maior benefício do RecyclerView é que ele é muito eficiente para listas grandes:

  • Por padrão, a RecyclerView só funciona para processar ou renderizar 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 realiza o trabalho suficiente para desenhar apenas os 10 itens na tela. Quando o usuário rola a página, o RecyclerView descobre quais itens precisam estar na tela e faz o trabalho suficiente para exibi-los.
  • Quando um item rola para fora da tela, as visualizações dele são recicladas. Isso significa que o item é preenchido com um novo conteúdo que aparece na tela. Esse comportamento do RecyclerView economiza muito tempo de processamento e ajuda as listas a rolarem de maneira fluida.
  • Quando um item muda, em vez de redesenhar a lista inteira, o RecyclerView pode atualizar apenas esse item. Isso é um grande ganho de eficiência ao mostrar 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 de adaptador

Se você viaja entre países que usam tomadas diferentes, provavelmente sabe como conectar seus dispositivos usando um adaptador. O adaptador permite converter um tipo de plugue em outro, o que realmente converte uma interface em outra.

O padrão de adaptador 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 possa mostrar, sem mudar a forma como o app armazena e processa os dados. Para o app de monitoramento do sono, você cria um adaptador que adapta os dados do banco de dados Room para algo que o RecyclerView sabe como mostrar, sem mudar o ViewModel.

Implementar um RecyclerView

Para mostrar seus dados em um RecyclerView, você precisa das seguintes partes:

  • Dados a serem mostrados.
  • Uma instância RecyclerView definida no arquivo de layout para atuar como o contêiner das visualizações.
  • Um layout para um item de dados.
    Se todos os itens da lista forem iguais, 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 para que uma visualização de item por vez possa ser criada e preenchida com dados.
  • Um gerenciador de layout.
    O gerenciador de layout processa a organização (o layout) dos componentes de UI em uma visualização.
  • Um fixador de visualização.
    O fixador de visualização estende a classe ViewHolder. Ele contém as informações de visualização para mostrar um item do layout. 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. Ele adapta os dados para que possam ser mostrados em um ViewHolder. Um RecyclerView usa o adaptador para descobrir como mostrar os dados na tela.

Nesta tarefa, você vai adicionar uma RecyclerView ao arquivo de layout e configurar um Adapter para expor os dados de sono à RecyclerView.

Etapa 1: adicionar RecyclerView com LayoutManager

Nesta etapa, você vai 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 mostrados como texto simples.
  3. Abra o arquivo de layout fragment_sleep_tracker.xml na guia Design do 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 a lista de tipos de componentes à esquerda para encontrar Containers e selecione essa opção.
  6. Arraste um RecyclerView do painel Paleta para o painel Árvore de componentes. Coloque o RecyclerView dentro do ConstraintLayout.

  1. Se uma caixa de diálogo perguntar se você quer adicionar uma dependência, clique em OK para permitir que o Android Studio adicione a dependência recyclerview ao arquivo do Gradle. Isso pode levar alguns segundos, e depois o app será sincronizado.

  1. Abra o arquivo build.gradle do módulo, role até o final e observe a nova dependência, que é semelhante ao código abaixo:
implementation 'androidx.recyclerview:recyclerview:1.0.0'
  1. Volte para fragment_sleep_tracker.xml.
  2. Na guia Texto, procure o código RecyclerView mostrado abaixo:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Atribua um id de sleep_list à RecyclerView.
android:id="@+id/sleep_list"
  1. Posicione o RecyclerView para ocupar a parte restante da tela dentro do ConstraintLayout. Para fazer isso, restrinja a parte de cima do RecyclerView ao botão Start, a parte de baixo ao botão Clear e cada lado ao elemento pai. Defina a largura e a altura do layout como 0 dp no Layout Editor 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 os itens na lista. O Android oferece um LinearLayoutManager, que por padrão organiza os itens em uma lista vertical de linhas de largura total.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
  1. Mude para a guia Design e observe que as restrições adicionadas fizeram com que o RecyclerView se expandisse para preencher o espaço disponível.

Etapa 2: criar o layout do item da lista e o suporte da visualização de texto

O RecyclerView é apenas um contêiner. Nesta etapa, você cria o layout e a infraestrutura para os itens serem exibidos no RecyclerView.

Para chegar a um RecyclerView funcional o mais rápido possível, use inicialmente um item de lista simplista que mostre apenas a qualidade do sono como um número. Para isso, você precisa de um suporte de visualização, TextItemViewHolder. Você também precisa de uma visualização, um TextView, para os dados. Em uma etapa posterior, você vai saber mais sobre os titulares de visualização e como apresentar todos os dados de sono.

  1. Crie um arquivo de layout chamado text_item_view.xml. Não importa o que você usa como elemento raiz, porque vai substituir o código do modelo.
  2. Em text_item_view.xml, exclua todo o código fornecido.
  3. Adicione um TextView com padding de 16dp no início e no fim e um tamanho de texto de 24sp. Deixe a largura corresponder ao elemento pai e a altura agrupar o conteúdo. Como essa visualização é mostrada dentro do RecyclerView, não é necessário colocar a visualização 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 Util.kt. Role até o final e adicione a definição mostrada abaixo, que cria a classe TextItemViewHolder. Coloque o código na parte de baixo do arquivo, depois da última chave de fechamento. O código vai em Util.kt porque esse holder de visualização é temporário e será substituído 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 principal tarefa ao implementar uma RecyclerView é criar o adaptador. Você tem um armazenador de visualização simples para a visualização de itens e um layout para cada item. Agora você pode criar um adaptador. O adaptador cria um fixador de visualização e o preenche com dados para que o RecyclerView seja exibido.

  1. No pacote sleeptracker, crie uma nova classe Kotlin com o nome SleepNightAdapter.
  2. Faça a classe SleepNightAdapter estender RecyclerView.Adapter. A classe é chamada de SleepNightAdapter porque adapta um objeto SleepNight em algo que RecyclerView pode usar. O adaptador precisa saber qual fixador de visualização usar. Portanto, transmita TextItemViewHolder. Importe os componentes necessários quando solicitado. Em seguida, um erro vai aparecer porque há métodos obrigatórios a serem implementados.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
  1. No nível superior de SleepNightAdapter, crie uma variável listOf SleepNight 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 mostrar, e isso é feito chamando getItemCount().
override fun getItemCount() = data.size
  1. Em SleepNightAdapter, substitua a função onBindViewHolder(), conforme mostrado abaixo.

    A função onBindViewHolder() é chamada por RecyclerView para mostrar os dados de um item da lista na posição especificada. Portanto, o método onBindViewHolder() usa dois argumentos: um fixador de visualização e uma posição dos dados a serem vinculados. Para este app, o marcador é TextItemViewHolder, e a posição é a posição na lista.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. Em 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. Em onBindViewHolder(), defina o text da textView como o número da qualidade do sono. Esse código mostra apenas uma lista de números, mas esse exemplo simples permite que você veja como o adaptador coloca os dados no holder 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 de um fixador de visualização para representar um item.

    Essa função usa dois parâmetros e retorna um ViewHolder. O parâmetro parent, que é o grupo de visualizações que contém o holder, é sempre o RecyclerView. O parâmetro viewType é usado quando há várias visualizações no mesmo RecyclerView. Por exemplo, se você colocar uma lista de visualizações de texto, uma imagem e um vídeo no mesmo RecyclerView, a função onCreateViewHolder() precisará saber qual 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 como criar visualizações com layouts XML. O context contém informações sobre como inflar a visualização corretamente. Em um adaptador para uma visualização de reciclagem, sempre transmita o contexto do grupo de visualização parent, que é o RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. Em onCreateViewHolder(), crie o view pedindo ao layoutinflater para inflar.

    Transmita o layout XML da visualização e o grupo de visualizações parent. O terceiro argumento, booleano, é 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 um TextItemViewHolder feito com view.
return TextItemViewHolder(view)
  1. O adaptador precisa informar ao RecyclerView quando o data muda, porque o RecyclerView não sabe nada sobre os dados. Ele só conhece os viewholders que o adaptador fornece.

    Para informar ao RecyclerView quando os dados que ele está mostrando mudarem, adicione um setter personalizado à variável data na parte de cima da classe SleepNightAdapter. No setter, atribua um novo valor a data e chame notifyDataSetChanged() para acionar a nova renderização da lista com os novos dados.
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

Etapa 4: informar o RecyclerView sobre o adaptador

O RecyclerView precisa saber qual adaptador usar para receber fixadores de visualização.

  1. Abra SleepTrackerFragment.kt.
  2. Em onCreateview(), crie um adaptador. Coloque esse código depois da 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 o projeto para atualizar o objeto binding.

    Se os erros relacionados a binding.sleepList ou binding.FragmentSleepTrackerBinding persistirem, invalide os caches e reinicie. Selecione Arquivo > Invalidar caches / Reiniciar.

    Se você executar o app agora, não haverá erros, mas nenhum dado será exibido quando você tocar em Iniciar e depois em Parar.

Etapa 5: inserir dados no adaptador

Até agora, você tem um adaptador e uma maneira de transferir dados do adaptador para a RecyclerView. Agora você precisa inserir dados no adaptador do ViewModel.

  1. Abra SleepTrackerViewModel.
  2. Encontre a variável nights, que armazena todas as noites de sono, ou seja, os dados a serem mostrados. A variável nights é definida chamando getAllNights() no banco de dados.
  3. Remova private de nights, porque você vai criar um observador que precisa acessar essa variável. Sua declaração precisa ser semelhante a esta:
val nights = database.getAllNights()
  1. No pacote database, abra SleepDatabaseDao.
  2. Encontre 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 é mantido atualizado por Room, e você pode observar nights para saber quando ele muda.
  3. Abra SleepTrackerFragment.
  4. Em onCreateView(), abaixo da criação do adapter, crie um observador na variável nights.

    Ao fornecer o viewLifecycleOwner do fragmento como proprietário do ciclo de vida, você garante que esse observador só fique ativo quando o 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 concluído para o observador e a definição dos dados:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. Crie e execute o código.

    Se o adaptador estiver funcionando, você vai 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: saiba como os titulares de visualização são reciclados

O RecyclerView recicla os fixadores de visualização, o que significa que ele os reutiliza. À medida que uma visualização rola para fora da tela, o RecyclerView a reutiliza para a visualização que está prestes a rolar para dentro da tela.

Como esses titulares de visualização são reciclados, verifique se onBindViewHolder() define ou redefine as personalizações que os itens anteriores podem ter definido em um titular de visualização.

Por exemplo, você pode definir a cor do texto como vermelho em elementos de visualização que contêm classificações de qualidade menores ou iguais a 1 e representam sono ruim.

  1. Na classe SleepNightAdapter, adicione o seguinte código ao final de onBindViewHolder().
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED) // red
}
  1. Execute o app.
  2. Adicione alguns dados de baixa qualidade de sono, e o número fica vermelho.
  3. Adicione classificações altas para a qualidade do sono até que um número vermelho alto apareça na tela.

    Como o RecyclerView reutiliza titulares de visualização, ele acaba reutilizando um dos titulares de visualização vermelhos para uma classificação de alta qualidade. A classificação alta é mostrada erroneamente em vermelho.

  1. Para corrigir isso, adicione uma instrução else para definir a cor como preta se a qualidade não for menor ou igual a um.

    Com as duas condições explícitas, o holder de visualização vai 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ê vai substituir o suporte de visualização simples por um que possa mostrar mais dados de uma noite de sono.

O ViewHolder simples que você adicionou ao Util.kt envolve um TextView em um TextItemViewHolder.

class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)

Então, por que o RecyclerView não usa um TextView diretamente? Essa única linha de código oferece muita funcionalidade. Um ViewHolder descreve uma visualização de item e metadados sobre o lugar dele no RecyclerView. O RecyclerView depende dessa funcionalidade para posicionar corretamente a visualização à medida que a lista rola e para fazer coisas interessantes, como animar visualizações 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 o itemView ao vincular 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ê vai criar o arquivo de layout para um item. O layout consiste em um ConstraintLayout com um 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 layouts antes, copie e cole o código XML fornecido.

  1. Crie um arquivo de recursos de layout e nomeie-o como list_item_sleep_night.
  2. Substitua todo o código no 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. Mude para a guia Design no Android Studio. Na visualização de design, seu layout vai ficar parecido com a captura de tela à esquerda abaixo. Na visualização de planta, ele aparece como na captura de tela à direita.

Etapa 2: criar ViewHolder

  1. Abra SleepNightAdapter.kt.
  2. Crie uma classe dentro de SleepNightAdapter chamada ViewHolder e faça com que ela estenda 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 esse ViewHolder vai atualizar. Sempre que você vincula esse ViewHolder, é necessário acessar a imagem e as duas visualizações de texto. Você vai converter 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 de SleepNightAdapter, em vez de TextItemViewHolder, use o SleepNightAdapter.ViewHolder que você acabou de criar.
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 a transmissão para TextView.
  4. Em vez de retornar um TextItemViewHolder, retorne um ViewHolder.

    Confira 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 de onBindViewHolder() para que o parâmetro holder seja um ViewHolder em vez de um TextItemViewHolder.
  2. Em onBindViewHolder(), exclua todo o código, exceto a definição de item.
  3. Defina um val res que contenha uma referência ao resources para essa visualização.
val res = holder.itemView.context.resources
  1. Defina o texto da visualização de texto sleepLength como a duração. 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 das importações associadas. (Selecione Code > Comment with Line comments.)
  2. De volta ao onBindViewHolder(), use convertNumericQualityToString() para definir a qualidade.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. Talvez seja necessário importar essas funções manualmente.
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. Confira a função onBindViewHolder() atualizada e concluída, definindo todos os dados para 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 vai ficar parecida com a captura de tela abaixo, mostrando o ícone de qualidade do sono, além do texto com a duração e a qualidade do sono.

O RecyclerView foi concluído. Você aprendeu a implementar um Adapter e um ViewHolder, e os juntou para mostrar uma lista com um RecyclerView Adapter.

Até agora, seu código mostra o processo de criação de um adaptador e um fixador de visualização. No entanto, é possível melhorar esse código. O código para mostrar e o código para gerenciar titulares de visualização estão misturados, e onBindViewHolder() sabe detalhes sobre como atualizar o ViewHolder.

Em um app de produção, você pode ter vários titulares de visualização, adaptadores mais complexos e vários desenvolvedores fazendo mudanças. Estruture o código para que tudo relacionado a um armazenador de visualização esteja apenas nele.

Etapa 1: refatorar onBindViewHolder()

Nesta etapa, você vai refatorar o código e mover toda a funcionalidade do holder 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 seguro 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 na palavra holder do parâmetro holder de bind(). Pressione Alt+Enter (Option+Enter em um Mac) para abrir o menu de intenção. Selecione Converter parâmetro em receptor para transformar em uma função de extensão com a seguinte assinatura:
private fun ViewHolder.bind(item: SleepNight) {...}
  1. Recorte e cole a função bind() no ViewHolder.
  2. Torne 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. Confira 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

O método onCreateViewHolder() no adaptador atualmente infla a visualização do recurso de layout para o ViewHolder. No entanto, a inflação não tem nada a ver com o adaptador, mas sim com o ViewHolder. A inflação deve ocorrer 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 no nome da função from. Pressione Alt+Enter (Option+Enter em um Mac) para abrir o menu de intenção.
  5. Selecione Mover para objeto complementar. A função from() precisa estar em um objeto complementar para que possa ser chamada na classe ViewHolder, não em uma instância ViewHolder.
  6. Mova o objeto companion para a classe ViewHolder.
  7. Torne 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 devem ficar parecidos com o código abaixo, e seu código precisa 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 ViewHolder, não há mais motivo para algué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 (link em inglês)

  • Mostrar uma lista ou grade de dados é uma das tarefas mais comuns da interface do usuário no Android. O RecyclerView foi projetado para ser eficiente mesmo ao exibir listas extremamente grandes.
  • RecyclerView só faz o trabalho necessário para processar ou renderizar itens que estão visíveis na tela no momento.
  • Quando um item rola para fora da tela, as visualizações dele são recicladas. Isso significa que o item é preenchido com um novo conteúdo que aparece na tela.
  • O padrão de adaptador 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 possa ser exibido, sem precisar mudar a forma como o app armazena e processa os dados.

Para mostrar seus dados em um RecyclerView, você precisa das seguintes partes:

  • RecyclerView
    Para criar uma instância de RecyclerView, defina um elemento <RecyclerView> no arquivo de layout.
  • LayoutManager
    Um RecyclerView usa um LayoutManager para organizar o layout dos itens no RecyclerView, como em uma grade ou em uma lista linear.

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

    Também é possível definir o LayoutManager para um RecyclerView de maneira programática. Essa técnica será abordada em um codelab posterior.
  • Layout para cada item
    Crie um layout para um item de dados em um arquivo de layout XML.
  • Adapter
    : crie um adaptador que prepare os dados e como eles serão mostrados em um ViewHolder. Associe o adaptador ao RecyclerView.

    Quando o RecyclerView é executado, ele usa o adaptador para descobrir como mostrar 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 da visualização para mostrar um item do layout dele.
  • O método onBindViewHolder() no adaptador adapta os dados às visualizações. Você sempre substitui esse método. Normalmente, o onBindViewHolder() aumenta 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 mudam. Use notifyDataSetChanged() para notificar o Adapter de que os dados mudaram.

Curso da Udacity:

Documentação do desenvolvedor Android:

Esta seção lista as possíveis atividades de dever de casa para os alunos que estão fazendo este codelab como parte de um curso ministrado por um professor. Cabe ao professor fazer o seguinte:

  • Atribuir o dever de casa, se necessário.
  • Informar aos alunos como enviar deveres de casa.
  • Atribuir nota aos deveres de casa.

Os professores podem usar essas sugestões o quanto quiserem, podendo passar os exercícios que acharem mais apropriados como dever de casa.

Se você estiver seguindo este codelab por conta própria, sinta-se à vontade para usar esses deveres de casa para testar seu conhecimento.

Responda estas perguntas

Pergunta 1

Como o RecyclerView exibe os 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 usar layouts personalizados quando uma lista ou grade não é o suficiente para o caso de uso.

Pergunta 2

Quais são os benefícios do uso do RecyclerView? Selecione todas as opções aplicáveis.

▢ Mostra listas grandes com eficiência.

▢ Atualiza os dados automaticamente.

▢ Minimiza a necessidade de mudanças quando um item é atualizado, excluído ou adicionado à lista.

▢ Reutiliza a visualização que rola para fora da tela para mostrar o próximo item que rola na tela.

Pergunta 3

Quais são alguns dos motivos para usar adaptadores? Selecione todas as opções aplicáveis.

▢ A separação de responsabilidades facilita a mudança e o teste do código.

RecyclerView é independente dos dados que estão sendo mostrados.

▢ As camadas de tratamento de dados não precisam se preocupar com a forma como os dados serão exibidos.

▢ O app será executado mais rapidamente.

Pergunta 4

Quais das seguintes opções é verdadeira sobre o ViewHolder? Selecione todas as opções aplicáveis.

▢ O layout ViewHolder é definido nos arquivos XML.

▢ Há um ViewHolder para cada unidade de dados no conjunto.

▢ É possível ter mais de um ViewHolder em um RecyclerView.

▢ O Adapter vincula dados ao ViewHolder.

Comece a próxima lição: 7.2: DiffUtil e vinculação de dados com o RecyclerView