Conceitos básicos do Kotlin para Android 07.2: DiffUtil e vinculação de dados com o 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

No codelab anterior, você atualizou o app TrackMySleepQuality para exibir dados sobre a qualidade do sono em uma RecyclerView. As técnicas que você aprendeu quando criou sua primeira RecyclerView são suficientes para a maioria das RecyclerViews que exibem listas simples que não são muito grandes. No entanto, existem várias técnicas que tornam o RecyclerView mais eficiente para listas grandes e facilitam a manutenção e a extensão do seu código para listas e grades complexas.

Neste codelab, você usará o app monitor de sono do codelab anterior. Você aprenderá uma maneira mais eficaz de atualizar a lista de dados de sono e aprenderá a usar a vinculação de dados com o RecyclerView. Se você não tiver o app do codelab anterior, faça o download do código inicial para este codelab.

O que você já precisa saber

  • Criação de uma interface do usuário básica usando uma atividade, fragmentos e visualizações.
  • Navegar entre fragmentos usando safeArgs para transmitir dados entre eles.
  • Veja modelos, visualizações de fábricas, transformações e LiveData e os respectivos observadores.
  • Como criar um banco de dados Room, um DAO e definir entidades.
  • Como usar corrotinas para banco de dados e outras tarefas de longa duração.
  • Como implementar um RecyclerView básico com um Adapter, ViewHolder e o layout do item.

O que você vai aprender

  • Como usar DiffUtil para atualizar de forma eficiente uma lista exibida por RecyclerView.
  • Como usar a vinculação de dados com RecyclerView.
  • Como usar adaptadores de vinculação para transformar dados.

Atividades do laboratório

  • Crie no app TrackMySleepQuality do codelab anterior desta série.
  • Atualize o SleepNightAdapter para atualizar a lista de maneira eficiente usando DiffUtil.
  • Implemente a vinculação de dados para a RecyclerView usando adaptadores de vinculação para transformar os dados.

O app monitor 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. A tela mostra alguns 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.

Este app foi arquitetado para usar um controlador de IU, ViewModel e LiveData, e um banco de dados Room para manter dados de sono.

Os dados de sono são exibidos em um RecyclerView. Neste codelab, você criará a DiffUtil e a parte de vinculação de dados para a RecyclerView. Após este codelab, seu app será exatamente igual, mas será mais eficiente e fácil de escalonar e manter.

Você pode continuar usando o app SleepTracker do codelab anterior ou pode fazer o download do app DiffDiffUtilDataBinding-Starter no GitHub.

  1. Se necessário, faça o download do app DiffDiffUtilDataBinding-Starter no GitHub e abra o projeto no Android Studio.
  2. Execute o app.
  3. Abra o arquivo SleepNightAdapter.kt.
  4. Inspecione o código para se familiarizar com a estrutura do app. Consulte o diagrama abaixo para recapitular o uso de RecyclerView com o padrão do adaptador para exibir dados de sono ao usuário.

  • Na entrada do usuário, o app cria uma lista de objetos SleepNight. Cada objeto SleepNight representa uma única noite de sono, a duração e a qualidade dele.
  • O SleepNightAdapter adapta a lista de objetos SleepNight em algo que RecyclerView pode usar e exibir.
  • O adaptador SleepNightAdapter produz ViewHolders que contém as visualizações, dados e informações meta para que a visualização de reciclagem exiba os dados.
  • A RecyclerView usa a SleepNightAdapter para determinar quantos itens há para exibir (getItemCount()). A RecyclerView usa a onCreateViewHolder() e a onBindViewHolder() para vincular os armazenadores de visualização aos dados a serem exibidos.

O método notifyDataSetChanged() é ineficiente.

Para informar ao RecyclerView que um item da lista foi alterado e precisa ser atualizado, o código atual chama notifyDataSetChanged() no SleepNightAdapter, conforme mostrado abaixo.

var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

No entanto, o notifyDataSetChanged() informa ao RecyclerView que a lista inteira é possivelmente inválida. Como resultado, a RecyclerView vincula e desenha novamente todos os itens da lista, incluindo os que não estão visíveis na tela. Esse é um trabalho desnecessário. Para listas grandes ou complexas, esse processo pode levar tempo o suficiente, de forma que a tela oscila ou oscila à medida que o usuário rola a lista.

Para corrigir esse problema, informe RecyclerView exatamente o que mudou. RecyclerView pode atualizar apenas as visualizações que foram alteradas na tela.

RecyclerView tem uma API avançada para atualizar um único elemento. Você pode usar notifyItemChanged() para informar a RecyclerView que um item foi alterado e usar funções semelhantes para itens adicionados, removidos ou movidos. Você poderia fazer tudo manualmente, mas essa tarefa seria complexa e poderia envolver bastante código.

Felizmente, existe um jeito melhor.

O DiffUtil é eficiente e faz o trabalho pesado para você.

RecyclerView tem uma classe chamada DiffUtil, que serve para calcular as diferenças entre duas listas. O DiffUtil usa uma lista antiga e uma nova e descobre o que é diferente. Ela localiza os itens adicionados, removidos ou alterados. Em seguida, ele usa um algoritmo chamado Eugene W. o algoritmo de diferença de Myers para descobrir o número mínimo de alterações a serem feitas na lista antiga para produzir a nova.

Depois que o DiffUtil descobre o que foi alterado, o RecyclerView pode usar essa informação para atualizar somente os itens que foram alterados, adicionados, removidos ou movidos. Isso é muito mais eficiente do que refazer a lista inteira.

Nesta tarefa, você fará upgrade do SleepNightAdapter para usar DiffUtil e otimizar o RecyclerView para mudanças nos dados.

Etapa 1: implementar SleepNightDiffCallback

Para usar a funcionalidade da classe DiffUtil, estenda DiffUtil.ItemCallback.

  1. Abra o SleepNightAdapter.kt
  2. Abaixo da definição de classe completa para SleepNightAdapter, crie uma nova classe de nível superior chamada SleepNightDiffCallback, que estenda DiffUtil.ItemCallback. Transmita SleepNight como um parâmetro genérico.
class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {
}
  1. Coloque o cursor no nome da classe SleepNightDiffCallback.
  2. Pressione Alt+Enter (Option+Enter no Mac) e selecione Implementar membros.
  3. Na caixa de diálogo exibida, clique com o botão esquerdo do mouse para selecionar os métodos areItemsTheSame() e areContentsTheSame(), depois clique em OK.

    Isso gera stubs dentro de SleepNightDiffCallback para os dois métodos, conforme mostrado abaixo. O DiffUtil usa esses dois métodos para descobrir como a lista e os itens foram alterados.
    override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
  1. Dentro de areItemsTheSame(), substitua a TODO pelo código que testa se os dois itens SleepNight transmitidos, oldItem e newItem, são iguais. Se os itens tiverem o mesmo nightId, serão o mesmo item, então retorne true. Caso contrário, retorne false. O DiffUtil usa esse teste para ajudar a descobrir se um item foi adicionado, removido ou movido.
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
   return oldItem.nightId == newItem.nightId
}
  1. No areContentsTheSame(), verifique se oldItem e newItem contêm os mesmos dados, ou seja, se eles são iguais. Essa verificação de igualdade verificará todos os campos porque SleepNight é uma classe de dados. As classes Data definem automaticamente o equals e alguns outros métodos para você. Se houver diferenças entre oldItem e newItem, esse código informará ao DiffUtil que o item foi atualizado.
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
   return oldItem == newItem
}

É um padrão comum usar um RecyclerView para exibir uma lista que muda. RecyclerView fornece uma classe de adaptador, ListAdapter, que ajuda a criar um adaptador RecyclerView que tem o suporte de uma lista.

O ListAdapter monitora a lista para você e notifica o adaptador quando a lista é atualizada.

Etapa 1: mudar o adaptador para estender o ListAdapter

  1. No arquivo SleepNightAdapter.kt, mude a assinatura da classe de SleepNightAdapter para estender ListAdapter.
  2. Se solicitado, importe androidx.recyclerview.widget.ListAdapter.
  3. Adicione SleepNight como o primeiro argumento ao ListAdapter antes de SleepNightAdapter.ViewHolder.
  4. Adicione SleepNightDiffCallback() como um parâmetro ao construtor. O ListAdapter usará isso para descobrir o que mudou na lista. A assinatura da classe SleepNightAdapter final precisa ficar assim:
class SleepNightAdapter : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
  1. Na classe SleepNightAdapter, exclua o campo data, incluindo o setter. Você não precisa mais desta lista, porque ListAdapter monitora a lista para você.
  2. Exclua a substituição de getItemCount(), porque a ListAdapter implementa esse método para você.
  3. Para eliminar o erro em onBindViewHolder(), mude a variável item. Em vez de usar data para conseguir um item, chame o método getItem(position) que o ListAdapter fornece.
val item = getItem(position)

Etapa 2: use sendList() para manter a lista atualizada

Seu código precisa informar a ListAdapter quando uma lista alterada estiver disponível. ListAdapter fornece um método com o nome submitList() para informar ao ListAdapter que uma nova versão da lista está disponível. Quando esse método é chamado, o ListAdapter diferencia a nova lista da antiga e detecta itens que foram adicionados, removidos, movidos ou alterados. Em seguida, o ListAdapter atualizará os itens mostrados por RecyclerView.

  1. Abra o SleepTrackerFragment.kt
  2. Em onCreateView(), no observador de sleepTrackerViewModel, localize o erro em que a variável data que você excluiu é referenciada.
  3. Substitua adapter.data = it por uma chamada para adapter.submitList(it). O código atualizado é mostrado abaixo.

sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.submitList(it)
   }
})
  1. Execute o app. Ele será executado mais rápido, talvez não perceptível se sua lista for pequena.

Nesta tarefa, você usa a mesma técnica dos codelabs anteriores para configurar a vinculação de dados e eliminar as chamadas para findViewById().

Etapa 1: adicionar a vinculação de dados ao arquivo de layout

  1. Abra o arquivo de layout list_item_sleep_night.xml na guia Text.
  2. Coloque o cursor na tag ConstraintLayout e pressione Alt+Enter (Option+Enter em um Mac). O menu de intenção (o menu "correção rápida") é aberto.
  3. Selecione Converter para layout de vinculação de dados. Isso envolve o layout em <layout> e adiciona uma tag <data>.
  4. Se necessário, role para a parte superior e, dentro da tag <data>, declare uma variável chamada sleep.
  5. Defina o type como o nome totalmente qualificado de SleepNight, com.example.android.trackmysleepquality.database.SleepNight. A tag <data> final ficará como o exemplo abaixo.
   <data>
        <variable
            name="sleep"
            type="com.example.android.trackmysleepquality.database.SleepNight"/>
    </data>
  1. Para forçar a criação do objeto Binding, selecione Build > Clean Project e Build > Rebuild Project. Se você ainda tiver problemas, selecione File > Invalidate Caches / Restart. O objeto de vinculação ListItemSleepNightBinding, com o código relacionado, é adicionado aos arquivos gerados pelo projeto.

Etapa 2: inflar o layout do item usando a vinculação de dados

  1. Abra o SleepNightAdapter.kt
  2. Na classe ViewHolder, encontre o método from().
  3. Exclua a declaração da variável view.

Código para excluir:

val view = layoutInflater
       .inflate(R.layout.list_item_sleep_night, parent, false)
  1. Onde a variável view estava, defina uma nova variável chamada binding que infla o objeto de vinculação ListItemSleepNightBinding, como mostrado abaixo. Faça a importação necessária do objeto de vinculação.
val binding =
ListItemSleepNightBinding.inflate(layoutInflater, parent, false)
  1. No final da função, em vez de retornar o view, retorne binding.
return ViewHolder(binding)
  1. Para eliminar o erro, coloque o cursor sobre a palavra binding. Pressione Alt+Enter (Option+Enter em um Mac) para abrir o menu de intents.
  1. Selecione Alterar parâmetro 'itemView' tipo de construtor principal da classe 'ViewHolder' para 'ListItemSleepNightBinding'. Isso atualiza o tipo de parâmetro da classe ViewHolder.

  1. Role para cima até a definição de classe do ViewHolder para ver a mudança na assinatura. Você verá um erro para itemView, porque alterou itemView para binding no método from().

    Na definição da classe ViewHolder, clique com o botão direito do mouse em uma das ocorrências de itemView e selecione Refactor > Rename. Mude o nome para binding.
  2. Adicione o prefixo binding ao parâmetro val do construtor para torná-lo uma propriedade.
  3. Na chamada para a classe pai, RecyclerView.ViewHolder, mude o parâmetro de binding para binding.root. É necessário transmitir um View, e binding.root é a ConstraintLayout raiz no layout do item.
  4. A declaração de classe finalizada ficará como o código abaixo.
class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root){

Você também verá um erro nas chamadas para findViewById() e o corrigirá em seguida.

Etapa 3: substituir findViewById()

Agora você pode atualizar as propriedades sleepLength, quality e qualityImage para usar o objeto binding em vez de findViewById().

  1. Mude as inicializações de sleepLength, qualityString e qualityImage para usar as visualizações do objeto binding, como mostrado abaixo. Depois disso, o código não mostrará mais erros.
val sleepLength: TextView = binding.sleepLength
val quality: TextView = binding.qualityString
val qualityImage: ImageView = binding.qualityImage

Com o objeto de vinculação em vigor, não é mais necessário definir as propriedades sleepLength, quality e qualityImage. O DataBinding armazenará as pesquisas em cache. Portanto, não é necessário declarar essas propriedades.

  1. Clique com o botão direito do mouse nos nomes das propriedades sleepLength, quality e qualityImage. Selecione Refatorar > Inline ou pressione Control+Command+N (Option+Command+N no Mac).
  2. Execute o app. Em caso de erros, talvez seja necessário limpar e recriar o projeto.

Nesta tarefa, você fará upgrade do seu app para usar a vinculação de dados com adaptadores de vinculação e definir os dados nas suas visualizações.

Em um codelab anterior, você usou a classe Transformations para usar LiveData e gerar strings formatadas para exibição em visualizações de texto. No entanto, se você precisar vincular tipos diferentes ou complexos, poderá fornecer adaptadores de vinculação para ajudar na vinculação de dados. Adaptadores de vinculação são adaptadores que adaptam seus dados para algo que pode ser usado para vincular uma visualização, como um texto ou uma imagem.

Você implementará três adaptadores de vinculação, um para a imagem de qualidade e outro para cada campo de texto. Em resumo, para declarar um adaptador de vinculação, você define um método que usa um item e uma visualização e insere a anotação @BindingAdapter. No corpo do método, você implementa a transformação. No Kotlin, você pode criar um adaptador de vinculação como uma função de extensão na classe de visualização que recebe os dados.

Etapa 1: criar adaptadores de vinculação

Você precisará importar várias classes na etapa, e ela não será chamada individualmente.

  1. Abra o SleepNightAdapater.kt
  2. Na classe ViewHolder, encontre o método bind() e lembre-se do que esse método faz. Você usará o código que calcula os valores de binding.sleepLength, binding.quality e binding.qualityImage e o usará dentro do adaptador. Por enquanto, deixe o código como está. Você o moverá em uma etapa posterior.
  3. No pacote sleeptracker, crie e abra um arquivo chamado BindingUtils.kt.
  4. Declare uma função de extensão em TextView, chamada setSleepDurationFormatted, e transmita uma SleepNight. Essa função será seu adaptador para calcular e formatar a duração do sono.
fun TextView.setSleepDurationFormatted(item: SleepNight) {}
  1. No corpo de setSleepDurationFormatted, vincule os dados à visualização, como você fez em ViewHolder.bind(). Chame convertDurationToFormatted() e defina text da TextView como o texto formatado. Como essa é uma função de extensão no TextView, você pode acessar a propriedade text diretamente.
text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, context.resources)
  1. Para informar a vinculação de dados sobre esse adaptador de vinculação, anote a função com @BindingAdapter.
  2. Essa função é o adaptador do atributo sleepDurationFormatted. Portanto, transmita sleepDurationFormatted como um argumento para @BindingAdapter.
@BindingAdapter("sleepDurationFormatted")
  1. O segundo adaptador define a qualidade do sono com base no valor em um objeto SleepNight. Crie uma função de extensão chamada setSleepQualityString() em TextView e transmita uma SleepNight.
  2. No corpo, vincule os dados à visualização, como você fez na ViewHolder.bind(). Chame convertNumericQualityToString e defina text.
  3. Anote a função com @BindingAdapter("sleepQualityString").
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight) {
   text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
  1. O terceiro adaptador de vinculação define a imagem em uma visualização. Crie a função de extensão em ImageView, chame setSleepImage e use o código de ViewHolder.bind(), conforme mostrado abaixo.
@BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight) {
   setImageResource(when (item.sleepQuality) {
       0 -> R.drawable.ic_sleep_0
       1 -> R.drawable.ic_sleep_1
       2 -> R.drawable.ic_sleep_2
       3 -> R.drawable.ic_sleep_3
       4 -> R.drawable.ic_sleep_4
       5 -> R.drawable.ic_sleep_5
       else -> R.drawable.ic_sleep_active
   })
}

Etapa 2: atualizar o SleepNightAdapter

  1. Abra o SleepNightAdapter.kt
  2. Exclua tudo no método bind(), porque agora você pode usar a vinculação de dados e os novos adaptadores para fazer esse trabalho para você.
fun bind(item: SleepNight) {
}
  1. Dentro de bind(), atribua a suspensão a item, porque você precisa informar o objeto de vinculação sobre o novo SleepNight.
binding.sleep = item
  1. Abaixo dessa linha, adicione binding.executePendingBindings(). Essa chamada é uma otimização que pede que a vinculação de dados execute imediatamente as vinculações pendentes. É sempre bom chamar executePendingBindings() ao usar adaptadores de vinculação em uma RecyclerView, já que isso pode acelerar um pouco o dimensionamento das visualizações.
 binding.executePendingBindings()

Etapa 3: adicionar vinculações ao layout XML

  1. Abra o list_item_sleep_night.xml
  2. No ImageView, adicione uma propriedade app com o mesmo nome do adaptador de vinculação que define a imagem. Transmita a variável sleep, conforme mostrado abaixo.

    Essa propriedade cria a conexão entre a visualização e o objeto de vinculação pelo adaptador. Sempre que o sleepImage é referenciado, o adaptador adapta os dados da SleepNight.
app:sleepImage="@{sleep}"
  1. Faça o mesmo para as visualizações de texto sleep_length e quality_string. Sempre que os métodos sleepDurationFormatted ou sleepQualityString são referenciados, os adaptadores adaptam os dados do SleepNight.
app:sleepDurationFormatted="@{sleep}"
app:sleepQualityString="@{sleep}"
  1. Execute o app. Ele funciona exatamente como antes. Os adaptadores de vinculação cuidam de todo o trabalho de formatação e atualização das visualizações à medida que os dados mudam, simplificando o ViewHolder e fornecendo o código muito mais estruturado do que antes.

Você exibiu a mesma lista para os últimos exercícios. Por padrão, isso mostra que a interface Adapter permite arquitetar o código de várias maneiras diferentes. Quanto mais complexo for o código, mais importante será arquitetá-lo bem. Em apps de produção, esses padrões e outros são usados com RecyclerView. Os padrões funcionam e cada um tem seus benefícios. A opção escolhida depende do que você está criando.

Parabéns! Você já está no caminho certo para dominar RecyclerView no Android.

Projeto do Android Studio: RecyclerViewDiffUtilDataBinding.

DiffUtil:

  • RecyclerView tem uma classe chamada DiffUtil, que serve para calcular as diferenças entre duas listas.
  • A DiffUtil tem uma classe chamada ItemCallBack que será estendida para descobrir a diferença entre duas listas.
  • Na classe ItemCallback, é necessário modificar os métodos areItemsTheSame() e areContentsTheSame().

ListAdapter:

  • Para ter um gerenciamento de listas sem custo financeiro, você pode usar a classe ListAdapter em vez de RecyclerView.Adapter. No entanto, se você usar a ListAdapter, precisará criar seu próprio adaptador para outros layouts. É por isso que este codelab mostra como fazer isso.
  • Para abrir o menu de intents no Android Studio, posicione o cursor sobre qualquer item do código e pressione Alt+Enter (Option+Enter no Mac). Esse menu é particularmente útil para refatorar código e criar stubs para implementar métodos. O menu é sensível ao contexto. Portanto, é necessário posicionar o cursor exatamente para chegar ao menu correto.

Vinculação de dados:

  • Use a vinculação de dados no layout do item para vincular dados às visualizações.

Como vincular adaptadores:

  • Você já usou Transformations para criar strings de dados. Se você precisar vincular dados de tipos diferentes ou complexos, forneça adaptadores de vinculação para ajudar na vinculação.
  • Para declarar um adaptador de vinculação, defina um método que use um item e uma visualização e inclua a anotação @BindingAdapter no método. No Kotlin, você pode programar o adaptador de vinculação como uma função de extensão no View. Transmita o nome da propriedade que o adaptador adapta. Exemplo:
@BindingAdapter("sleepDurationFormatted")
  • No layout XML, defina uma propriedade app com o mesmo nome do adaptador de vinculação. Transmita uma variável com os dados. Exemplo:
.app:sleepDurationFormatted="@{sleep}"

Cursos da Udacity:

Documentação do desenvolvedor Android:

Outros recursos:

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

Quais das opções a seguir são necessárias para usar DiffUtil? Selecione todas as opções aplicáveis.

▢ Ampliar a classe ItemCallBack.

▢ Modificar areItemsTheSame().

▢ Modificar areContentsTheSame().

▢ Use vinculação de dados para rastrear as diferenças entre os itens.

Pergunta 2

Quais das alternativas a seguir são verdadeiras sobre os adaptadores de vinculação?

▢ Um adaptador de vinculação é uma função com uma anotação @BindingAdapter.

▢ Usar um adaptador de vinculação permite separar a formatação dos dados do armazenador de visualização.

▢ É necessário usar um RecyclerViewAdapter se você quiser usar adaptadores de vinculação.

▢ Os adaptadores de vinculação são uma boa solução quando é preciso transformar dados complexos.

Pergunta 3

Quando usar Transformations em vez de um adaptador de vinculação? Selecione todas as opções aplicáveis.

▢ Seus dados são simples.

▢ Você está formatando uma string.

▢ Sua lista é muito longa.

▢ Seu ViewHolder contém apenas uma visualização.

Inicie a próxima lição: 7.3: GridLayout com o RecyclerView