Aprenda a usar o RecyclerView no Android (Kotlin)

O que é uma Recycler View?

Antigamente, nós usávamos um elemento do tipo ListView no Android para criar uma lista de tabelas na nossa aplicação.

Um dos problemas principais com as listView é que elas criavam e mantinham salvas cada um dos elementos que poderiam existir dentro de uma lista.

Sendo assim, supondo que temos uma lista de mais de 1 milhão de elementos, 1 milhão de elementos seriam carregados e permaneceriam salvos na memoria, enquanto aquela Activity que carrega a listView ainda se encontra aberta para o usuário.

Com o passar dos anos, surgiu um novo método de fazer listagens no Android, e isso se deu por meio da RecyclerView.

Ela nada mais é do que uma forma otimizada a performática de se carregar listas no Android (tabelas).

A grande diferença, é que ela consegue reciclar os itens da tabela, de forma a tirar da memoria itens que estão escondidos para o usuário.

Nesse caso, vamos imaginar que temos a mesma lista com mais de 1 milhão de elementos criados por meio da RecyclerView, como toda lista, é impossível listar dentro de uma única tela todos os 1 milhão de elementos, certo? É por isso que usamos a barra de rolagem.

Quando usamos a barra de rolagem, certos elementos deixam de aparecer na tela, e é aí que a RecyclerView entra.

Quando um elemento some do campo de visão daquele usuário, automaticamente aquele item é reciclado para o próximo, de modo a otimizar a quantidade de memoria do celular do usuário.

Em um cenário onde usamos uma listView, os itens ainda continuariam alocados na memoria mesmo após eles saírem do campo de visão do usuário.

Você pode fazer o download do repositório deste conteúdo acessando este link.

Trabalhando com a RecyclerView

Antes de iniciarmos a construção das nossas tabelas, tenha em mente que diferente de um TextView, EditView, ImageView onde só precisamos declarar - ou arrastar, caso você estiver usando a aba 'Design' para criação dos Layouts-,  esses elementos via código de forma simples.

No caso da RecyclerView, não existe uma forma simples e enxuta de implementação, como vemos em outros elementos do Android.

Nela você vai precisar fazer alguns passos (envolvendo referências via código) para que ela funcione.

Para darmos inicio ao projeto, vamos criar um novo projeto com o Android Studio, indo em File > New... > New Project.

Na aba de template, selecione as opções Phone and Tablet > Empty Views Activity, para que possamos trabalhar da forma mais simples possível.

Observação: Tenha em mente que o objetivo deste conteúdo é criar uma recyclerview da forma mais simples possível, sem integrações com banco de dados, arquivos de dados, ou qualquer outro template/padrão/elemento do Android.

Com o arquivo main_activity.xml aberto, vamos declarar um novo elemento do tipo RecyclerView cujo o ID será "recycler_nomes", identificando que será uma lista de nomes.

Neste exemplo vamos usar o ConstraintsLayout:

<?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="match_parent"
android:padding="8dp"
tools:context=".MainActivity">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_nomes"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Veja como ficou o Layout no modo de visualização do Android:

Item 0? Item 1? Fique tranquilo pois é só um template de como irá ser mostrado os itens para o seu usuário. O Android Studio faz isso para sabermos que aquele elemento se trata de uma lista.

Bem, agora que temos a integração inicial da RecyclerView, precisamos entender 2 coisas super importantes:

  • Não é possível atribuir uma lista de elementos diretamente a RecyclerView, usando o um findById da vida, ou quem sabe um template Binding.
  • Precisamos criar e configurar um arquivo de Adaptador e outro arquivo de ViewHolder, para que a nossa RecyclerView funciona corretamente.

Lembra que anteriormente foi dito que uma RecyclerView não dispõe de uma integração fácil? É isso 😅

Definindo nossa lista de Nomes

Como nossa lista vai salvar somente nome de pessoas, podemos fazer isso por meio de um listOf que será definido dentro do arquivo MainActivity.kt:

class MainActivity : AppCompatActivity() {

val listaDeNomes: List<String> = listOf("Micilini Roll", "Gabriel Solano", "Frederico Barlett")

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

Na nossa lista, vamos importar inicialmente 3 nomes, mas você pode adicionar mais se quiser.

Criando a Estrutura da Recycler View

Partindo agora para a estrutura da RecylerView, antes de mais nada é essêncial que configuremos nosso Binding.

Portanto, no arquivo de Gradle (Module: App), não se esqueça de definir o BuildFeatures:

android {
...
buildFeatures {
viewBinding true
}
}

Bastando apenas clicar no botão "Sync Now", para sincronizar a nova funcionalidade do seu projeto.

Legal, viewBinding instalada com sucesso e agora temos que "bindar" a nossa Main Activity, e fazemos isso deixando ela da seguinte forma:

class MainActivity : AppCompatActivity() {

val listaDeNomes: List<String> = listOf("Micilini Roll", "Gabriel Solano", "Frederico Barlett")
private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)//Instancia o ViewBinding
setContentView(binding.root)//Seta a view para a bindingRoot
}
}

Binding feito, show 🥳

Agora chegou a hora de colocar a mão na massa e fazer a RecyclerView funcionar.

Definindo o Layout

Quando definimos o Layout relacionado a RecyclerView, não estamos definindo o layout interno em si, mas a forma como eles vão se comportar.

De modo que podemos informar qual o arquivo .xml será carregado múltiplas vezes dentro da nossa RecyclerView.

Sendo assim, dentro do método OnCreate da MainActivity.kt, precisamos declarar o layoutManager que é responsável por gerenciar a nossa lista:

//Layout da RecyclerView
binding.recyclerNames.layoutManager = LinearLayoutManager(this)//Estamos usando o formato de Layout mais simples para criar essa View

Definindo o Adapter

Agora precisamos definir um adaptador para a nossa lista, e podemos fazer isso ainda dentro do OnCreate da MainActivity.kt:

//Adapter da RecyclerView
binding.recyclerNames.adapter = NomesAdapter()

NomesAdapter é uma instância de uma classe que ainda vamos criar nos próximos passos.

Mas por hora, precisamos entender o conceito principal entre o Layout e o Adapter de uma RecyclerView.

Layout & Adapter

Com o LayoutManager da RecyclerView, o Android sabe como ela se comporta, sabendo como ordenar e disponibilizar cada item dentro da lista.

Já o Adapter funciona da seguinte forma, pense que na sua mão direita você tem a lista de nomes, e na sua mão esquerda você tem o Layout em si.

Daí você se pergunta: "De que forma eu posso juntar os dois?".

A resposta para isso é o Adapter, e dentro da classe NomesAdapter vamos entender mais a fundo como eles se conectam.

Classe do Adaptador (NomesAdapter)

A classe responsável por adaptar a sua lista de nomes (que existe dentro da variável listaDeNomes) com o Layout da lista (que ainda vamos criar) se chama Adapter, e ela por padrão se estende da classe RecycleView.Adapter.

Que por sua vez, nos obriga a declarar 3 métodos principais:

OnCreateViewHolder: Ele é responsável pela criação do Layout da Lista, onde para cada item da lista que é carregado, este método será chamado.

Considerando que cada elemento da nossa lista seja um TextView, o Kotlin irá criar um TextView para cada item existente na lista (multiplicando).

O bom desse método é que em alguns projetos, talvez tenhamos itens com Layouts diferentes, e com esse método podemos trata-los.

OnBindViewHolder: Ele é responsável por fazer a cola de forma a atribuir os valores para o seu Layout.

getItemCount: Ele é responsável por retornar o tamanho total de itens que devem ser listados na RecyclerView.

Antes de criar a nossa classe NomesAdapter, vamos criar uma nova Package (pasta) dentro da pasta principal do seu projeto (Eu criei dentro de com.example.recyclerview).

Para isso, basta clicar com o botão direito na pasta principal (com.example.recyclerview), e ir em New > Package, digitando o nome "adapter" para criar uma pasta.

Fizemos isso para organizar os arquivos que serão criados daqui pra frente.

Agora dentro dessa pasta que você acabou de criar, precisamos criar a classe NomesAdapter, e podemos fazer isso clicando com o botão direito em cima da pasta "adapter" e ir em New > Kotlin/Class File, criando um arquivo chamado NomesAdapter.kt

package com.example.recyclerview.adapter

class NomesAdapter {
}

Após a criação da classe inicial, precisamos estender de RecyclerView.Adapter implementando também seus 3 métodos principais (OnCreateViewHolder, OnBindViewHoldergetItemCount):

package com.example.recyclerview.adapter

import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

class NomesAdapter: RecyclerView.Adapter<VH>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
TODO("Not yet implemented")
}

override fun onBindViewHolder(holder: VH, position: Int) {
TODO("Not yet implemented")
}

override fun getItemCount(): Int {
TODO("Not yet implemented")
}
}

Como vimos no código anterior, talvez o seu compilador tenha identificado um erro quando declaramos a classe VH.

VH nada mais é do que uma sigla para a nossa classe de ViewHolder que ainda iremos criar 😉

Classe do ViewHolder (NomesViewHolder)

Ainda dentro do diretório principal do projeto, vamos criar um outro Package (pasta) chamado "viewHolder", e dentro dele uma nova classe chamada NomesViewHolder.

Acredito que essa altura do campeonato você já sabia como criar uma package e uma classe de kotlin, não?

Com a classe de ViewHolder criada, precisamos estende-la de RecyclerView.ViewHolder, da seguinte forma:

package com.example.recyclerview.viewholder

import android.view.View
import androidx.recyclerview.widget.RecyclerView

class NomesViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {

}

Em seguida volte para a classe NomesAdapter e troque o VH para NomesViewHolder.

package com.example.recyclerview.adapter

import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.recyclerview.viewholder.NomesViewHolder

class NomesAdapter: RecyclerView.Adapter<NomesViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NomesViewHolder {
TODO("Not yet implemented")
}

override fun onBindViewHolder(holder: NomesViewHolder, position: Int) {
TODO("Not yet implemented")
}

override fun getItemCount(): Int {
TODO("Not yet implemented")
}
}

Para que possamos entender melhor, a ViewHolder é a classe que tem a referência para os seus elementos de Interface que você ainda vai criar.

Lembra que foi dito que precisamos criar mais um arquivo .xml que representará cada item que será multiplicado dentro da RecyclerView?

Então, dentro desse arquivo .xml, podemos existir um TextView que represente o nome da pessoa, como também outros elementos como ImageView (Foto da Pessoa) e muitos outros.

E advinha quem se comunica com esses elementos por meio de ids? Sim, a nossa ViewHolder!

Criando o Layout da Listagem

Chegou o momento de criarmos o Layout dos itens que irão ser multiplicado dentro da nossa lista.

E para isso, precisamos criar um novo arquivo .xml, dentro da pasta Res > Layout.

Clique com o botão direito na pasta Layout e vá em: New > Layout Resource File.

Repare que eu criei um arquivo chamado row_nomes.xml, onde row significa "linha" em inglês.

Perceba que da forma como nós criamos esse Layout, ele não está relacionado com nenhum dos arquivos que criamos anteriormente.

Fique tranquilo que posteriormente iremos instanciar (inflar) esse novo layout dentro do nosso Adapter 🤓

Veja como ficou o arquivo row_nomes.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">

<TextView
android:id="@+id/text_nome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nome do Usuário"
android:textSize="24sp" />

</LinearLayout>

Pois bem, com a estrutura inicial já criada, podemos partir para a fase dois, que é a implementação da lista de nomes dentro da nossa tabela.

🏁 Marco 0: Você chegou ao marco do row_nomes.xml, isso significa que você realizou todos os procedimentos para a implementação (não é mais necessário mexer neste arquivo).  

Viu como não tem um jeito simples de se trabalhar com listas no Android 😓

Listando nomes na RecyclerView

Com a estrutura pronta, a primeira coisa que precisamos fazer é jogar a nossa lista de nomes para dentro da classe do Adapter, pois é ela quem define quais valores serão inseridos nos itens (o nome das pessoas em si), e quantas linhas serão multiplicadas (a quantidade de itens existente dentro da variável listaDeNomes).

Para isso vamos começar fazendo pequenas modificações no arquivo do MainActivity.kt.

MainActivity

Dentro do arquivo do Adapter (NomesAdapter), temos que ter em mente que devemos receber a lista do MainActivity para que possamos usa-la.

Portanto dentro do arquivo MainActivity.kt, que é o arquivo que contém a lista de nomes, vamos precisar fazer algumas modificações:

Passo 1) Precisamos tornar a instancia do Adapter (NomesAdapter) global, para que nossos futuros métodos acessem e modifiquem um único Adapter:

private val adapter = NomesAdapter()

Passo 2) Trocar a referência dessa variável para o adapter da RecyclerView:

//Adapter da RecyclerView
binding.recyclerNames.adapter = adapter

Agora que já temos uma única instancia fazendo referência ao nosso Adapter, que tal chamarmos a função responsável por receber nossa lista?

//Passa a lista de nomes para o Adapter
adapter.atualizaNomes(listaDeNomes)

A chamada acima foi declarada na última linha do método OnCreate do MainActivity.

🏁 Marco 1: Você chegou ao marco do MainActivity, isso significa que você realizou todos os procedimentos para a implementação (não é mais necessário mexer neste arquivo).

package com.example.recyclerview

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.recyclerview.adapter.NomesAdapter
import com.example.recyclerview.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

//Abaixo definimos a lista de nomes que serão carregados pela RecyclerView
val listaDeNomes: List<String> = listOf("Micilini Roll", "Gabriel Solano", "Frederico Barlett")
private lateinit var binding: ActivityMainBinding
private val adapter = NomesAdapter()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)//Instancia o ViewBinding
setContentView(binding.root)//Seta a view para a bindingRoot

//Para estruturarmos uma RecycleView precisamos definir seu Layout e também um Adapter:

//Layout da RecyclerView
binding.recyclerNames.layoutManager = LinearLayoutManager(this)//Estamos usando o formato de Layout mais simples para criar essa View

//Adapter da RecyclerView
binding.recyclerNames.adapter = adapter

//Passa a lista de nomes para o Adapter
adapter.atualizaNomes(listaDeNomes)

}
}

Classe do Adapter (NomesAdapter)

Dentro da sua classe de Adapter, precisamos criar um método que seja capaz de receber a lista de nomes:

private var listaNomes: List<String> = listOf()//Instanciamos uma lista vazia para que não quebre a lógica do RecycleView.
//Fizemos isso porque o getItemCount espera um valor inicial.

fun atualizaNomes(list: List<String>){
listaNomes = list
}

Agora você será capaz de retornar o número de itens dentro do método getItemCount():

override fun getItemCount(): Int {
return listaNomes.count()//Retorna a contagem de elementos existente em listaNomes
}

Já dentro do método onCreateViewHolder, ele espera que retornem um elemento do tipo ViewHolder.

Nesse caso precisamos inflar o Layout do row_nomes.xml, de modo que o RecyclerView saiba qual layout ele deve renderizar nos itens da lista.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NomesViewHolder {
val item = RowNomesBinding.inflate(LayoutInflater.from(parent.context), parent, false)//Para inflar o row_nomes.xml, precisamos usar o método inflate
//Mas para isso a classe precisa ser do tipo Fragment ou Activity, e no nosso caso estamos dentro de um RecyclerView, o que nos impossíbilita de inflar.
//Por sorte, dentro dos parâmetros deste método, temos o parent, que salva o contexto de onde essa classe foi chamada (MainActivity).
//Nesse caso é só usarmos o comando do LayoutInflater.from (onde from significa 'onde'), passando o context do parent.
//Em seguida passando o parent que contém outras informações da ViewGroup e um terceiro parâmetro chamado attachToParent.
//Quando o attachToParent vem setado como true, ele vai criar o layout e já associar na recyclerView, e como não queremos controlar isso, deixamos como false.

return NomesViewHolder(item.root)//Retorna uma instancia de ViewHolder, onde dentro dela passamos o elemento Raiz
}

Agora dentro do método OnCreateViewHolder, que é chamado logo após o método OnCreateViewHolder, ela é responsável para atribuir valores.

Repare que é retornado para a gente, dois parâmetros, o primeiro é a nossa ViewHolde e o segundo é a posição do elemento.

Basta apenas que chamemos um método do ViewHolder para atribuir ao nosso TextView o valor da posição do item que existem em cada elemento do nosso listaNomes:

override fun onBindViewHolder(holder: NomesViewHolder, position: Int) {
holder.bind(listaNomes[position])
}

Por fim, caso precisar, você pode chamar o método notifyDataSetChanged(), que diz para a RecycleView atualizar seus dados novamente, e essa chamada pode existir dentro do nosso método atualizaNomes:

fun atualizaNomes(list: List<String>){
listaNomes = list
notifyDataSetChanged()//Indica a RecycleView para que ela atualize seus dados novamente...
}

🏁 Marco 2: Você chegou ao marco do NomesAdapter, isso significa que você realizou todos os procedimentos para a implementação (não é mais necessário mexer neste arquivo).

package com.example.recyclerview.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.recyclerview.databinding.RowNomesBinding
import com.example.recyclerview.viewholder.NomesViewHolder

class NomesAdapter: RecyclerView.Adapter<NomesViewHolder>() {

private var listaNomes: List<String> = listOf()//Instanciamos uma lista vazia para que não quebre a lógica do RecycleView.
//Fizemos isso porque o getItemCount espera um valor inicial.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NomesViewHolder {
val item = RowNomesBinding.inflate(LayoutInflater.from(parent.context), parent, false)//Para inflar o row_nomes.xml, precisamos usar o método inflate
//Mas para isso a classe precisa ser do tipo Fragment ou Activity, e no nosso caso estamos dentro de um RecyclerView, o que nos impossíbilita de inflar.
//Por sorte, dentro dos parâmetros deste método, temos o parent, que salva o contexto de onde essa classe foi chamada (MainActivity).
//Nesse caso é só usarmos o comando do LayoutInflater.from (onde from significa 'onde'), passando o context do parent.
//Em seguida passando o parent que contém outras informações da ViewGroup e um terceiro parâmetro chamado attachToParent.
//Quando o attachToParent vem setado como true, ele vai criar o layout e já associar na recyclerView, e como não queremos controlar isso, deixamos como false.

return NomesViewHolder(item)//Retorna uma instancia de ViewHolder, onde dentro dela passamos o elemento Raiz
}

override fun onBindViewHolder(holder: NomesViewHolder, position: Int) {
holder.bind(listaNomes[position])
}

override fun getItemCount(): Int {
return listaNomes.count()//Retorna a contagem de elementos existente em listaNomes
}

fun atualizaNomes(list: List<String>){
listaNomes = list
notifyDataSetChanged()//Indica a RecycleView para que ela atualize seus dados novamente...
}
}

Classe do ViewHolder (NomesViewHolder)

Anteriormente nós vimos que o método OnCreateViewHolder, utiliza um método da nossa classe ViewHolder para passar a lista de elementos, tal método chamado bind().

E agora precisamos cria-lo....

É nele que nos iremos receber os dados do Adapter, de modo a atualizar os elementos da nossa View. 

E existem duas formas de se fazer isso, a primeira delas é fazendo o uso do findViewById:

fun bind(nome: String){
//Representa a ligação do elemento de Interface com seus dados.
//A variável nome contém o nome de um dos elementos da nossa lista (que pode ser: "Micilini Roll", "Gabriel Solano" ou "Frederico Barlett").
//Ou seja, a função bind() é como se ela fosse executada dentro de um loop, onde a cada laço, viesse um nome diferente. E quem executa esse loop? O método onBindViewHolder() da classe do Adapter quando ele monta a lista

/* Método de Chamada 1 */
//Existe uma maneira fácil de selecionar o nosso TextView do arquivo row_nomes.xml com o intuito de atribuir o texto a ele
//Podemos fazer isso usando o findViewById:

itemView.findViewById<TextView>(R.id.text_nome).text = nome

//No comando acima estamos selecionando o itemView que armazena uma instancia (inflate) do row_nomes.xml. Essa instancia foi inflada pelo método onCreateViewHolder() na classe do Adapter.
}

segunda forma envolve modificações nos construtores da classe, modificações estas que fazem com que possamos selecionar os ids das nossas views por meio do ViewBinding:

package com.example.recyclerview.viewholder

import androidx.recyclerview.widget.RecyclerView
import com.example.recyclerview.databinding.RowNomesBinding

class NomesViewHolder(private val bind: RowNomesBinding): RecyclerView.ViewHolder(bind.root) {

fun bind(nome: String){
//Representa a ligação do elemento de Interface com seus dados.
//A variável nome contém o nome de um dos elementos da nossa lista (que pode ser: "Micilini Roll", "Gabriel Solano" ou "Frederico Barlett").
//Ou seja, a função bind() é como se ela fosse executada dentro de um loop, onde a cada laço, viesse um nome diferente. E quem executa esse loop? O método onBindViewHolder() da classe do Adapter quando ele monta a lista

/* Método de Chamada 2 */
//Aqui estamos usando o padrão viewBinding
bind.textNome.text = nome

//Agora temos acesso ao RowNomesBinding para ter um acesso padronizado aas views do row_nomes.xml
}

}

Lembrando que: Se você seguir com esse segundo método, você deverá realizar modificações no arquivo do Adapter, mais especificamente no retorno do método onCreateViewHolder:

return NomesViewHolder(item)//Não precisamos mais retornar 'item.root', pois o ViewHolder funciona de forma diferente agora

🏁 Marco 3: Você chegou ao marco do NomesViewHolder, isso significa que você realizou todos os procedimentos para a implementação (não é mais necessário mexer neste arquivo).

package com.example.recyclerview.viewholder

import androidx.recyclerview.widget.RecyclerView
import com.example.recyclerview.databinding.RowNomesBinding

class NomesViewHolder(private val bind: RowNomesBinding): RecyclerView.ViewHolder(bind.root) {

fun bind(nome: String){
//Representa a ligação do elemento de Interface com seus dados.
//A variável nome contém o nome de um dos elementos da nossa lista (que pode ser: "Micilini Roll", "Gabriel Solano" ou "Frederico Barlett").
//Ou seja, a função bind() é como se ela fosse executada dentro de um loop, onde a cada laço, viesse um nome diferente. E quem executa esse loop? O método onBindViewHolder() da classe do Adapter quando ele monta a lista

/* Método de Chamada 2 */
//Aqui estamos usando o padrão viewBinding
bind.textNome.text = nome

//Agora temos acesso ao RowNomesBinding para ter um acesso padronizado aas views do row_nomes.xml
}

}

Resultado da RecyclerView

Se você rodar o aplicativo no emulador do Android, verá que a sua tabela irá carregar normalmente com todos os nomes que você inseriu na sua lista.

Fazendo um breve resumo rápido de como implementar a RecyclerView na sua Aplicação, podemos resumir em alguns pequenos passos:

Passo 1°) Implementação do elemento RecyclerView dentro da sua Activity ou Fragment.

Passo 2°) Conectar o LayoutManager e sua classe de Adapter na sua RecyclerView, isso dentro da classe da sua Activity ou Fragment.

Passo 3°) Criar o layout (XML) que irá representar cada item da sua lista.

Passo 4°) Criar a classe de Adapter com os 3 métodos principais, e um método para recuperar a lista de itens.

Passo 5°) Criar a classe de ViewHolder, para conectar os itens na sua View.

No más, você pode acompanhar cada um dos passos acima no início deste conteúdo, mas calma que nossos estudos sobre RecyclerView ainda não terminaram 😆

RecyclerView dentro de um Fragment

Anteriormente nós vimos como criar e trabalhar com uma RecyclerView dentro de uma Activity.

Mas e se caso a RecyclerView precisar existir dentro de um Fragment?

Pois bem, como você já deve saber (assim espero), um Fragment nada mais é do que um componente bem parecido com uma Activity, que existe para ser inserido dentro de uma Activity principal.

Fazendo uma analogia com o HTML, nós temos uma página principal que conta com algumas tags do HTML como divs, headers, footers e afins. 

Também fazer o uso das famosas tags do tipo iFrame, que foram criados com o intuito de abrir outras páginas da web dentro de uma.

Sendo assim uma Activity está para uma página em HTML, e um Fragment está para um iFrame.

Quando criamos um Fragment no Android, o Kotlin nos dá por padrão, uma classe que conta com dois métodos principais:

  • onCreateView.
  • onDestroyView.

Sabendo que o método OnCreateView se comporta de forma muito similar com o OnCreate de uma Activity, basta que você faça as devidas declarações ali dentro:

//Abaixo definimos a lista de nomes que serão carregados pela RecyclerView
val listaDeNomes: List<String> = listOf("Micilini Roll", "Gabriel Solano", "Frederico Barlett")
private lateinit var binding: ActivityMainBinding
private val adapter = NomesAdapter()

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, b: Bundle?): View {

viewModel = ViewModelProvider(this).get(AllGuestsViewModel::class.java)

_binding = FragmentAllGuestsBinding.inflate(inflater, container, false)

//Layout da RecyclerView
binding.recyclerNames.layoutManager = LinearLayoutManager(context)//Estamos usando o formato de Layout mais simples para criar essa View

//Adapter da RecyclerView
binding.recyclerNames.adapter = adapter

//Passa a lista de nomes para o Adapter
adapter.atualizaNomes(listaDeNomes)

return binding.root
}

A uníca diferença é que ao invés de passarmos o this dentro da instância de LinearLayoutManager, passamos o context diretamente.

Com isso, sua RecyclerView irá funcionar normalmente dentro da sua Fragment.

Capturando cliques na RecyclerView

Uma das funcionalidades mais interessantes que podemos atrelar a nossa RecyclerView, é a capacidade de saber quando o usuário clicou em algum item existente na lista, e em que posição este item estava.

Considerando o projeto que criamos logo acima usando uma RecyclerView com a MainActivity.

A primeira coisa que devemos fazer é abrir a nossa ViewHolder (NomesViewHolder) e dentro do método bind(), configurar um ClickListener(), sobre nosso TextView.

fun bind(nome: String){

....

//Configurações do Clique no TextView

bind.textNome.setOnClickListener{
//Ações do Clique....
}

}

É importante ressaltar que o StartActivity dentro de um ViewHolder não funciona, pois ela não tem as bibliotecas necessárias (assim como as Activity) parta funcionar.

Uma vez que a nossa ViewHolder não dispõe do ciclo de vida do Android, e talvez nem tenha contexto.

Nesse caso, quem deve tratar tudo o que é relacionado ao contexto de navegação, são Activitys e Fragments, sendo assim, o que podemos fazer é chamar um método da nossa Activity (MainActivity) para que de lá esse método seja capaz de tomar as ações cabíveis.

E nós podemos fazer isso por meio de uma classe anônima.

Passo 1°) Crie uma nova interface que conterá os métodos que serão chamados via classe anônima na nossa Activity.

Portanto, na pasta principal do seu projeto crie um novo Package chamado de "listener".

Dentro desse pacote crie um novo arquivo do tipo Kotlin Class/File chamado de "OnNomesListener.kt" que será do tipo Interface e irá implementar um único método chamado onClick():

package com.example.recyclerview.listener

//Esta Interface é um "Truque" que usamos para que a nossa "MainActivity" responda algum clique advindo do "setOnClickListener" existente no arquivo NomesViewHolder.kt
//Onde o "setOnClickListener" chama o método onClick() toda vez que o usuário clica em algum TextView
//Ao mesmo tempo que todas as classes que implementam essa interface (OnNomesListener) mesmo que de forma anônima, tenham seus métodos onClick executados.

interface OnNomesListener {
fun onClick()
}

Passo 2°) Modificar a classe de ViewHolder (NomesViewHolder) para estender a Interface OnNomesListener para que possamos chamar o método onClick():

class NomesViewHolder(private val bind: RowNomesBinding, private val listener: OnNomesListener): RecyclerView.ViewHolder(bind.root) {
//Observe um novo parâmetro chamado listener que veio no método construtor da classe.


fun bind(nome: String){
//Representa a ligação do elemento de Interface com seus dados.
//A variável nome contém o nome de um dos elementos da nossa lista (que pode ser: "Micilini Roll", "Gabriel Solano" ou "Frederico Barlett").
//Ou seja, a função bind() é como se ela fosse executada dentro de um loop, onde a cada laço, viesse um nome diferente. E quem executa esse loop? O método onBindViewHolder() da classe do Adapter quando ele monta a lista

/* Método de Chamada 2 */
//Aqui estamos usando o padrão viewBinding
bind.textNome.text = nome

//Agora temos acesso ao RowNomesBinding para ter um acesso padronizado aas views do row_nomes.xml

//Configurações do CLique no TextView

bind.textNome.setOnClickListener{
listener.onClick()//Chamamos o método OnClick da Interface
//Não precisamos ter o código do onClick implementado, pois quem está fornecendo a ViewHolder nos construtores dessa classe, é que vai implementar o listener.
}

}

Passo 3°) Modificar a classe do Adapter (NomesAdapter) para que ela consiga receber e passar o OnNomesListener:

private lateinit var listener: OnNomesListener//Criamos um atributo do tipo listener para que possamos passa-lo para o ViewHolder

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NomesViewHolder {
val item = RowNomesBinding.inflate(LayoutInflater.from(parent.context), parent, false)//Para inflar o row_nomes.xml, precisamos usar o método inflate
//Mas para isso a classe precisa ser do tipo Fragment ou Activity, e no nosso caso estamos dentro de um RecyclerView, o que nos impossíbilita de inflar.
//Por sorte, dentro dos parâmetros deste método, temos o parent, que salva o contexto de onde essa classe foi chamada (MainActivity).
//Nesse caso é só usarmos o comando do LayoutInflater.from (onde from significa 'onde'), passando o context do parent.
//Em seguida passando o parent que contém outras informações da ViewGroup e um terceiro parâmetro chamado attachToParent.
//Quando o attachToParent vem setado como true, ele vai criar o layout e já associar na recyclerView, e como não queremos controlar isso, deixamos como false.

return NomesViewHolder(item, listener)//Retorna uma instancia de ViewHolder, onde dentro dela passamos o elemento Raiz e o OnNomesListener (pois agora se faz necessário)
}

...

fun attachListener(nomesListener: OnNomesListener){
//Função responsável por receber o Listener por parâmetro e salvar nos atributos da classe
listener = nomesListener
}

Passo 4°) No MainActivity precisamos instanciar a classe anônima do OnNomesListener, implementar o método obrigatório chamado onClick() e por fim passar essa instância para o Adapter:

override fun onCreate(savedInstanceState: Bundle?) {
....

//Define a classe anônima responsável por receber o clique
val listener = object : OnNomesListener{
override fun onClick() {
Toast.makeText(applicationContext, "Fui clicado", Toast.LENGTH_SHORT).show()
}
}

//Passa a implementação do listener para o Adapter
adapter.attachListener(listener)

....
}

No exemplo do código acima, nós executamos uma mensagem via Toast a cada vez que um item é clicado.

Como identificar o item que foi clicado na RecyclerView

Anteriormente nos vimos como chamar um método de uma função anônima por meio de uma interface que foi instanciada na classe principal (MainActivity) e repassada para as subclasses até chegar na ViewHolder. 

Legal, e muito interessante.

Mas como eu posso saber qual item foi clicado?

Na classe de ViewHolder (NomesViewHolder), mais especificamente no método bind(), você deve ter percebido que ela recebe o parâmetro nome que é do tipo String.

Sabendo disso, basta passar esse parâmetro quando for chamar o método onClick():

listener.onClick(nome)

Obviamente que você vai precisar modificar também o método da Interface para receber um parâmetro do tipo String:

fun onClick(nome: String)

E também o método anônimo:

val listener = object : OnNomesListener{
override fun onClick(nome: String) {
Toast.makeText(applicationContext, "Fui clicado: $nome", Toast.LENGTH_SHORT).show()
}
}

Dessa forma, ele mostrará a mensagem com o nome do item que foi clicado.

E se quisermos passar a posição do item?

Simples, basta que você vá no Adapter e passe para o método bind() o parâmetro position:

override fun onBindViewHolder(holder: NomesViewHolder, position: Int) {
//Este é responsável por fazer a cola de forma a atribuir os valores para o seu Layout.
//É neste método que acontece o loop. A quantidade de vezes que esse método será executada equivale ao retorno do "listaNomes.count()", declarado no método abaixo (getItemCount)

holder.bind(listaNomes[position], position)//Além do nome, estamos passando também o position do item
}

Ah e não se esqueça de tratar no bind() o segundo parâmetro 😁

Conclusão

Neste artigo você aprendeu um pouco mais sobre o funcionamento de RecyclerView e como ele pode te ajudar a criar listas recicláveis nos seus aplicativos Android.

Além dos conhecimentos adquiridos aqui, você pode adicionar botões no Layout da sua lista de modo a criar outras funcionalidades.

Ou até mesmo partir para conceitos mais avançados como fazer o uso da biblioteca gesture atrelado a RecycleView.