Introdução

Este tutorial ensina como usar um RecyclerView para exibir listas de itens. Com base no aplicativo sleep-tracker da série anterior de tutoriais, você aprende uma maneira melhor e mais versátil de exibir dados, usando um RecyclerView com uma arquitetura recomendada.

O que você já deveria saber

Você deve estar familiarizado com:

O que aprenderá

O que fará

Neste tutorial, você constrói a parte RecyclerView de um aplicativo que rastreia a qualidade do sono. O aplicativo usa um banco de dados Room para armazenar dados de sono ao longo do tempo.

O aplicativo starter sleep-tracker possui duas telas, representadas por fragmentos, conforme mostrado na figura abaixo.

A primeira tela, mostrada à esquerda, possui botões para iniciar e parar o rastreamento. Esta tela também mostra todos os dados de sono do usuário. O botão Clear exclui permanentemente todos os dados que o aplicativo coletou para o usuário. A segunda tela, mostrada à direita, é para selecionar uma classificação de qualidade do sono.

Este aplicativo usa uma arquitetura simplificada com um controlador de IU, ViewModel e LiveData. O aplicativo também usa um banco de dados Room para tornar os dados de sono persistentes.

A lista de noites de sono exibida na primeira tela é funcional, mas não é bonita. O aplicativo usa um formatador complexo para criar strings de texto para a vista de texto e números para a qualidade. Além disso, este design não pode ser escalado. Depois de corrigir todos esses problemas neste tutorial, o aplicativo final tem a mesma funcionalidade e a tela principal fica assim:

Exibindo uma lista ou grade de dados é uma das tarefas de IU mais comuns no Android. As listas variam de simples a muito complexas. Uma lista de vistas 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 suportar todos esses casos de uso, o Android fornece o widget RecyclerView.

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

Na sequência mostrada abaixo, você pode ver que uma vista foi preenchida com dados, ABC. Depois que essa vista rola para fora da tela, RecyclerView reutiliza a vista para novos dados, XYZ.

O padrão do adaptador

Se você já viajou entre países que usam tomadas elétricas diferentes, provavelmente sabe como conectar seus dispositivos nas tomadas usando um adaptador. O adaptador permite converter um tipo de plugue em outro, o que, na verdade, converte uma interface em outra.

O padrão do adaptador na engenharia de software ajuda um objeto a trabalhar com outra API. RecyclerView usa um adaptador para transformar os dados do aplicativo em algo que o RecyclerView pode exibir, sem alterar como o aplicativo armazena e processa os dados. Para o aplicativo sleep-tracker, você constrói um adaptador que adapta os dados do banco de dados Room em algo que o RecyclerView sabe como exibir, sem alterar o ViewModel.

Implementando um RecyclerView

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

Nesta tarefa, você adiciona um RecyclerView ao seu arquivo de layout e configura um Adapter para expor os dados de sono ao RecyclerView.

Etapa 1: Adicione RecyclerView com LayoutManager

Nesta etapa, você substitui o ScrollView por um RecyclerView no arquivo fragment_sleep_tracker.xml.

  1. Baixe o aplicativo RecyclerViewFundamentals-Starter no GitHub.
  2. Construa e execute o aplicativo. 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 a ScrollView. Esta ação também exclui o TextView que está dentro do ScrollView.
  5. No painel Palette, role pela lista de tipos de componentes à esquerda para localizar Containers e selecione-o.
  6. Arraste uma RecyclerView do painel Palette para o painel Component Tree. Coloque o RecyclerView dentro do ConstraintLayout.

  1. Se uma caixa de diálogo for aberta perguntando se você deseja adicionar uma dependência, clique em OK para permitir que o Android Studio adicione a dependência recyclerview ao seu arquivo Gradle. Pode demorar alguns segundos e, em seguida, o aplicativo é sincronizado.

  1. Abra o arquivo do módulo build.gradle, 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 Text, procure o código RecyclerView mostrado abaixo:
<androidx.recyclerview.widget.RecyclerView
   android:layout_width="match_parent"
   android:layout_height="match_parent" />
  1. Dê ao RecyclerView um id de sleep_list.
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 superior do RecyclerView ao botão Start, a parte inferior ao botão Clear e cada lado ao pai. Defina a largura e 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 informa 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. 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: Crie o layout do item de lista e o recipiente de vistas de texto

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

Para chegar a um RecyclerView funcional o mais rápido possível, primeiro você usa um item de lista simples que exibe apenas a qualidade do sono como um número. Para isso, você precisa de um recipiente de vistas, TextItemViewHolder. Você também precisa de uma vista, uma TextView, para os dados. (Em uma etapa posterior, você aprenderá mais sobre os recipientes de vistas e como organizar todos os dados do sono).

  1. Crie um arquivo de layout chamado text_item_view.xml. Não importa o que você usa como elemento raiz, pois você substituirá o código do modelo.
  2. Em text_item_view.xml, exclua todo o código fornecido.
  3. Adicione um TextView com preenchimento de 16dp no início e no final, e um tamanho de texto de 24sp. Deixe que a largura corresponda à do pai e a altura envolva o conteúdo. Como essa vista é exibida dentro do RecyclerView, você não precisa colocar a vista dentro de um ViewGroup.

<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 inferior do arquivo, após a última chave de fechamento. O código vai para Util.kt, pois este recipiente de vistas é temporário e você o substitui mais tarde.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
  1. Se for solicitado, importe android.widget.TextView e androidx.recyclerview.widget.RecyclerView.

Etapa 3: Crie SleepNightAdapter

A principal tarefa na implementação de um RecyclerView é criar o adaptador. Você tem um recipiente de vistas simples para a vista do item e um layout para cada item. Agora você pode criar um adaptador. O adaptador cria um recipiente de vistas e o preenche com dados para que o RecyclerView exiba.

  1. No pacote sleeptracker, crie uma classe Kotlin chamada SleepNightAdapter.
  2. Faça com que a classe SleepNightAdapter estenda RecyclerView.Adapter. A classe é chamada de SleepNightAdapter, pois adapta um objeto SleepNight em algo que o RecyclerView pode usar. O adaptador precisa saber qual recipiente de vistas usar, então passe TextItemViewHolder. Importe os componentes necessários quando solicitado e você verá um erro, pois existem métodos obrigatórios para implementar.
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 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 por RecyclerView para exibir os dados de um item da lista na posição especificada. Portanto, o método onBindViewHolder() leva dois argumentos: Um recipiente de vistas e uma posição dos dados a serem vinculados. Para este aplicativo, o recipiente é o TextItemViewHolder, e a posição é a posição na lista.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
  1. Dentro de 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 o text de textView como o número da qualidade do sono. Este código exibe apenas uma lista de números, mas este exemplo simples permite que você veja como o adaptador obtém os dados no recipiente de vistas e na tela.
holder.textView.text = item.sleepQuality.toString()
  1. Em SleepNightAdapter, substitua e implemente onCreateViewHolder(), que é chamado quando o RecyclerView precisa de um recipiente de vistas para representar um item.

    Esta função recebe dois parâmetros e retorna um ViewHolder. O parâmetro parent, que é o grupo de vistas que mantém o recipiente da vista, é sempre o RecyclerView. O parâmetro viewType é usado quando há várias vistas no mesmo RecyclerView. Por exemplo, se você colocar uma lista de vistas de texto, uma imagem e um vídeo no mesmo RecyclerView, a função onCreateViewHolder() precisará saber qual tipo de vistas para 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 vistas a partir de layouts XML. O context contém informações sobre como inflar a vista corretamente. Em um adaptador para uma vista recicladora, você sempre passa no contexto do gripo de vistas parent, que é o RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
  1. Em onCreateViewHolder(), crie a view pedindo ao layoutinflater para inflá-la.

    Passe o layout XML para a vista e o gripo de vistas parent para a vista. O terceiro argumento, booleano, é attachToRoot. Este argumento precisa ser false, pois RecyclerView adiciona este item à hierarquia de vistas para você quando for a hora.
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 deixar o RecyclerView saber quando data mudou, pois o RecyclerView não sabe nada sobre os dados. Ele somente conhece os recipientes de vistas que o adaptador fornece.

    Para informar ao RecyclerView quando os dados que ele está exibindo foram alterados, adicione um configurador personalizado à variável data que está no topo da classe SleepNightAdapter. No setter, provenha data um novo valor e, em seguida, chame notifyDataSetChanged() para disparar o redesenho da lista com os novos dados.
var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

Etapa 4: Informe o RecyclerView sobre o adaptador

O RecyclerView precisa saber sobre o adaptador a ser usado para obter recipientes de vistas.

  1. Abra SleepTrackerFragment.kt.
  2. Em onCreateview(), crie um adaptador. Coloque este 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 reconstrua seu projeto para atualizar o objeto binding.

    Se você ainda vir erros em torno de binding.sleepList ou binding.FragmentSleepTrackerBinding, invalide os caches e reinicie. (Selecione File > Invalidate Caches / Restart).

    Se você executar o aplicativo agora, não haverá erros, mas você não verá nenhum dado exibido quando tocar em Start e, em seguida, Stop.

Etapa 5: Coloque os dados no adaptador

Até agora você tem um adaptador e uma maneira de obter dados do adaptador para o RecyclerView. Agora você precisa obter dados para o adaptador do ViewModel.

  1. Abra SleepTrackerViewModel.
  2. Encontre 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, pois você criará um observador que precisa acessar esta variável. Sua declaração deve ser assim:
val nights = database.getAllNights()
  1. No pacote database, abra o SleepDatabaseDao.
  2. Encontre a função getAllNights(). Observe que esta 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 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 o proprietário do ciclo de vida, você pode ter certeza de que este observador somente estará ativo quando o RecyclerView estiver na tela.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   })
  1. Dentro do observador, sempre que você obtiver um valor não nulo (para nights), atribua o valor aos data do adaptador. Este é o código completo para o observador e configuração dos dados:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.data = it
   }
})
  1. Construa e execute seu código.

    Você verá os números da qualidade do sono como uma lista, se o adaptador estiver funcionando. A captura de tela à esquerda mostra -1 após você tocar em Start. A captura de tela à direita mostra o número atualizado da qualidade do sono depois de tocar em Stop e selecionar uma classificação de qualidade.

Etapa 6: Explore como os recipientes de vistas são reciclados

RecyclerView recicla recipientes de vistas, o que significa que ele os reutiliza. Conforme uma vista rola para fora da tela, RecyclerView reutiliza a vista para a vista que está prestes a rolar na tela.

Como esses recipientes de vistas são reciclados, certifique-se de que onBindViewHolder() defina ou redefina quaisquer personalizações que os itens anteriores possam ter definido em um recipiente de vistas.

Por exemplo, você pode definir a cor do texto para vermelho em recipiente de vistas que possuem classificações de qualidade menores ou iguais a 1 e representam sono insatisfatório.

  1. Na classe SleepNightAdapter, adicione o seguinte código ao final de onBindViewHolder().
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED)
}
  1. Execute o aplicativo.
  2. Adicione alguns dados qualidade de sono com baixo valor e o número ficará vermelho.
  3. Adicione classificações altas para a qualidade do sono até ver um número alto em vermelho na tela.

    À medida que RecyclerView reutiliza recipiente de vistas, ele eventualmente reutiliza um dos recipientes de vistas vermelhos para uma classificação de alta qualidade. A classificação alta é exibida erroneamente 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 ambas as condições explícitas, o recipiente da vista usará o cor correta do texto para cada item.
if (item.sleepQuality <= 1) {
   holder.textView.setTextColor(Color.RED)
} else {
   holder.textView.setTextColor(Color.BLACK)
}
  1. Execute o aplicativo e os números devem sempre ter a cor correta.

Parabéns! Agora você tem um RecyclerView básico funcional.

Nesta tarefa, você substitui o recipiente de vistas simples por um que pode exibir mais dados para uma noite de sono.

O ViewHolder simples que você adicionou ao 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? Esta linha de código fornece muitas funcionalidades. Um ViewHolder descreve uma vista de item e metadados sobre seu lugar no RecyclerView. RecyclerView depende dessa funcionalidade para posicionar corretamente a vista conforme a lista rola e para fazer algo interessante como vistas animadas quando itens são adicionados ou removidos no Adapter.

Se o RecyclerView precisar acessar as vistas armazenadas no ViewHolder, ele pode fazer isso usando a propriedade do do recipiente de itemView. RecyclerView usa itemView ao vincular um item a ser exibido na tela, ao desenhar decorações ao redor de uma vista como uma borda e para implementar acessibilidade.

Etapa 1: Crie o layout do item

Nesta etapa, você cria 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 do texto. Como você já fez layouts antes, copie e cole o código XML fornecido.

  1. Crie um arquivo de recurso de layout e nomeie-o list_item_sleep_night.
  2. Substitua todo o código do arquivo pelo código abaixo. Em seguida, familiarize-se com o layout que acabou de criar.

<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 vista de design, seu layout se parece com a captura de tela à esquerda abaixo. Na vista do blueprint, parece a captura de tela à direita.

Etapa 2: Crie ViewHolder

  1. Abra SleepNightAdapter.kt.
  2. Faça uma classe dentro de SleepNightAdapter chamada ViewHolder e faça-a estender RecyclerView.ViewHolder.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. Dentro de ViewHolder, obtenha referências para as vistas. Você precisa de uma referência às vistas que este ViewHolder atualizará. Cada vez que você vincula este ViewHolder, você precisa acessar a imagem e ambas as vistas de texto. (Você converte este código para usar a vinculação de dados posteriormente).
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: Use ViewHolder no SleepNightAdapter

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

Atualize onCreateViewHolder():

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

    Aqui está 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. Altere a assinatura de 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 para a definição de item.
  3. Defina um val res que contém uma referência aos resources para esta vista.
val res = holder.itemView.context.resources
  1. Defina o texto da vista de texto sleepLength para 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, pois convertDurationToFormatted() precisa ser definido. Abra Util.kt e descomente o código e as importações associadas a ele. (Selecione Code > Comment with Line comments).
  2. De volta a onBindViewHolder(), use convertNumericQualityToString() para definir a qualidade.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. Você pode precisar 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. Aqui está a função onBindViewHolder() atualizada, configurando todos os dados para o 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 seu aplicativo. Sua tela deve ser semelhante à captura de tela abaixo, mostrando o ícone de qualidade do sono, junto com o texto para a duração e a qualidade do sono.

Seu RecyclerView agora está completo! Você aprendeu como implementar um Adapter e um ViewHolder e os colocou juntos para exibir uma lista com um RecyclerView Adapter.

Seu código até agora mostra o processo de criação de um adaptador e recipiente de vistas. No entanto, você pode melhorar este código. O código a ser exibido e o código para gerenciar os proprietários de vistas são misturados e onBindViewHolder() sabe os detalhes sobre como atualizar o ViewHolder.

Em um aplicativo de produção, você pode ter vários recipientes de vistas, adaptadores mais complexos e vários desenvolvedores fazendo alterações. Você deve estruturar seu código de forma que tudo relacionado a um recipiente de vistas esteja apenas no recipiente de vistas.

Etapa 1: Refatore onBindViewHolder()

Nesta etapa, você refatora o código e move toda a funcionalidade do recipiente de vistas para o ViewHolder. O objetivo dessa refatoração não é alterar a aparência do aplicativo 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 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 Convert parameter to receiver para convertê-lo 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. Torne bind() público.
  3. Importe bind() para o adaptador, se necessário.
  4. Como agora está no ViewHolder, você pode remover a parte ViewHolder da assinatura. Aqui está o código final para a 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: Refatore onCreateViewHolder

O método onCreateViewHolder() no adaptador atualmente infla a vista do recurso de layout para o 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 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 Move to companion object. A função from() precisa estar em um objeto complementar para que possa ser chamada na classe ViewHolder, não chamada em uma instância de ViewHolder.
  6. Mova o objeto companion para a classe ViewHolder.
  7. Torne from() público.
  8. Em onCreateViewHolder(), altere a instrução return para retornar o resultado da chamada de from() na classe ViewHolder.

    Seus métodos onCreateViewHolder() e from() concluídos devem ser semelhantes ao código abaixo, e seu código deve ser construído 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. Altere a assinatura da classe ViewHolder para que o construtor seja privado. Como from() agora é um método que retorna uma nova instância de ViewHolder, não há mais razão para ninguém chamar o construtor de ViewHolder.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
  1. Execute o aplicativo. Seu aplicativo deve construir e executar da mesma forma que antes, que é o resultado desejado após a refatoração.

Projeto Android Studio: RecyclerViewFundamentals

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

Documentação para desenvolvimento em Android:

Esta seção lista as possíveis tarefas de casa para os alunos que estão trabalhando neste tutorial como parte de um curso ministrado por um instrutor.

Responda a essas perguntas

Pergunta 1

Como o RecyclerView exibe os itens? Selecione tudo que se aplica.

▢ Exibe itens em uma lista ou grade.

▢ Rola verticalmente ou horizontalmente.

▢ Rola diagonalmente em dispositivos maiores, como tablets.

▢ Permite layouts personalizados quando uma lista ou grade não é suficiente para o caso de uso.

Pergunta 2

Quais são os benefícios de usar o RecyclerView? Selecione tudo que se aplica.

▢ Exibe listas grandes com eficiência.

▢ Atualiza os dados automaticamente.

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

▢ Reutiliza a vista que rola para fora da tela para exibir o próximo item que rola na tela.

Pergunta 3

Quais são algumas das razões para usar adaptadores? Selecione tudo que se aplica.

▢ A separação de interesses torna mais fácil alterar e testar o código.

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

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

▢ O aplicativo será executado mais rápido.

Pergunta 4

Quais das seguintes afirmações são verdadeiras para ViewHolder? Selecione tudo que se aplica.

▢ O layout ViewHolder é definido em arquivos de layout XML.

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

▢ Você pode ter mais de um ViewHolder em um RecyclerView.

▢ O Adapter vincula os dados ao ViewHolder.

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