Android RecyclerView: O que é, aprenda com exemplos simples
O que é o RecyclerView Android?
A Visualização do Reciclador é um widget que é uma versão mais flexível e avançada de GridView e ListView. É um contêiner para exibir grandes conjuntos de dados que podem ser rolados de forma eficiente, mantendo um número limitado de visualizações. Você pode usar o widget RecyclerView quando tiver coleções de dados cujos elementos mudam em tempo de execução dependendo do evento de rede ou da ação do usuário.
Visualizações
A Android plataforma usa as classes View e ViewGroup para desenhar itens na tela. Essas classes são abstratas e estendidas para diferentes implementações para atender a um caso de uso. TextView, por exemplo, tem o propósito simples de exibir conteúdo de texto na tela. EditText se estende da mesma classe View e adiciona mais funcionalidades para permitir que o usuário insira dados.
É possível criar nossas próprias visualizações personalizadas para obter mais flexibilidade no desenvolvimento de interfaces de usuário. A classe View fornece métodos que podemos substituir para desenhar na tela e um meio de passar parâmetros como largura, altura e nossos próprios atributos personalizados que gostaríamos de adicionar à nossa View para fazê-la se comportar como gostaríamos.
Ver grupos
A classe ViewGroup é um tipo de View, mas, ao contrário da classe View simples, cuja responsabilidade é simplesmente exibir, o ViewGroup nos dá a capacidade de colocar múltiplas visualizações em uma visualização, que podemos referenciar como um todo. Neste caso, a View criada no nível superior à qual adicionamos outras visualizações simples (também podemos adicionar viewGroups) é chamada de “pai”, e as visualizações adicionadas dentro são “filhas”.
Podemos imaginar uma View como um Array e um ViewGroup como um Array de Arrays. Dado que um Array de Arrays é um Array em si, podemos ver como um ViewGroup pode ser tratado como uma View.
var arr1 = [1,2,3] //imagine a simple View as an Array //we can imagine this as a NumberTextView which doesn't really exist //but we could imagine there's one that makes it easy to use numbers var arr2 = ["a","b","c"] // We can imagine this as another simple view var nestedArr = [arr1,arr2] //in our anology, we can now group views //together and the structure that would hold that would be what we call the ViewGroup
O ViewGroup também nos permite definir como os filhos são organizados dentro da visualização, por exemplo, são colocados verticalmente ou horizontalmente. Podemos ter diferentes regras de interação dentro da View. Por exemplo, TextViews um após o outro devem ter uma distância de 12dp, enquanto ImageViews seguidos por TextView devem ter uma distância de 5dp.
Este seria o caso se estivéssemos desenvolvendo nosso próprio ViewGroup do zero. Para facilitar essas configurações, Android fornece uma classe chamada LayoutParams, que podemos usar para inserir essas configurações.
Android documentação fornece alguns parâmetros padrão que implementaríamos ao configurar nosso próprio ViewGroup. Alguns parâmetros comuns são aqueles que dizem respeito à largura, altura e margem. Por padrão, essas configurações têm a estrutura android:layout_height para altura, por exemplo, android:layout_width Nesse sentido, ao criar seu ViewGroup, você pode criar ainda mais LayoutParams específicos para a maneira como deseja que seu ViewGroup se comporte.
Android vem com visualizações e grupos de visualizações padrão que podemos usar para realizar muitas das tarefas comuns de que precisamos. Um exemplo que mencionamos é um TextView. Essa é uma visualização simples que vem com aspectos configuráveis como altura, largura, textSize e similares. Temos um ImageView para exibir Imagens e EditText como mencionamos, entre muitos outros. Android também possui ViewGroups personalizados aos quais podemos adicionar nossas Views e obter o comportamento esperado.
Layout Linear
LinearLayout nos permite adicionar itens de visualização nele. LinearLayout é um atributo de orientação que determina como será disposto na tela. Ele também possui LinearLayout.LayoutParams que ditam as regras para as visualizações internas, por exemplo, o atributo android:center_horizontal centralizaria as visualizações ao longo do eixo horizontal, enquanto `android:center_vertical centralizaria o conteúdo da visualização ao longo do eixo vertical.
Aqui estão algumas imagens para você entender a centralização. Consideraríamos que isso era um TextView simples dentro de um espaço de 200 por 200 pixels, centralizar atributos faria com que ele se comportasse da seguinte maneira.
android:centro_horizontal

android:centro_vertical

andróide:centro

Componentes principais do RecyclerView

A seguir estão os componentes importantes do RecyclerView:
RecyclerView.Adapter
Base #1 – Padrão Adaptador
Um adaptador é um dispositivo que transforma atributos de um sistema ou dispositivo naqueles de um dispositivo ou sistema incompatível. Alguns deles modificam atributos de sinal ou potência, enquanto outros simplesmente adaptam a forma física de um conector para outro.
Um exemplo simples que encontramos na vida real para explicar um Adaptador é quando precisamos conectar dispositivos entre si, mas eles possuem portas de conexão que não combinam entre si. Este pode ser o caso quando você visita um país diferente onde eles usam diferentes tipos de tomadas. Se você carregar seu telefone ou carregador de laptop, será impossível conectá-lo às tomadas elétricas. No entanto, você não desistiria, simplesmente adquiriria um adaptador que ficaria entre a tomada elétrica e o carregador e permitiria que o carregamento acontecesse.
Este é o caso da programação quando queremos conectar duas estruturas de dados para cumprir a tarefa, mas suas portas padrão não têm como se comunicar entre si.
Usaremos o exemplo simples de um dispositivo e um carregador. Teremos duas instâncias de carregadores. Um americano e um britânico
class AmericanCharger() { var chargingPower = 10 } class BritishCharger(){ var charginPower = 5 }
Em seguida, criaremos dois dispositivos
class AmericanDevice() class BritishDevice()
Como exemplo, podemos então criar algumas instâncias dos dispositivos para tocar junto.
var myAmericanPhone = new AmericanDevice() var myBritishPhone = new BritishDevice()
Em seguida, apresentaremos o conceito de cobrança para ambos os dispositivos adicionando um método nos dispositivos chamado charge() .
O método recebe como entrada o respectivo carregador e carrega com base nele.
sealed trait Device class AmericanDevice : Device{ fun charge(charger:AmericanCharger){ //Do some American charging } } class BritishDevice: Device{ fun charge(charger:BritishCharger){ //Do some British charging } }
Nesse caso, com base em nossa analogia, por um motivo ou outro precisaríamos usar um BritishCharger ao usar um AmericanDevice ou vice-versa.
No mundo da programação, isso geralmente ocorre quando se misturam bibliotecas que oferecem a mesma funcionalidade (em nosso contexto, nossa funcionalidade compartilhada é cobrada). Precisaríamos encontrar uma maneira de permitir isso.
Se seguirmos a analogia, precisaremos ir a uma loja de eletrônicos e comprar um adaptador que nos permitiria carregar dispositivos americanos com carregadores britânicos. Do ponto de vista da programação, seremos nós os fabricantes do adaptador.
Faremos um adaptador para um que corresponda ao padrão exato que precisaríamos para criar o outro. Iremos implementá-lo como uma classe da seguinte maneira. Não precisa necessariamente ser uma classe e pode ser uma função que destaca o que o padrão do adaptador geralmente faz. Usaremos uma classe que corresponda à maior parte do uso em Android.
class AmericanToBritishChargerAdapter(theAmericanCharger:AmericanCharger){ fun returnNewCharger(): BritishCharger{ //convert the American charger to a BritishCharger //we would change the American charging functionality //to British charging functionality to make sure the //adapter doesn't destroy the device. The adapter could //, for example, control the power output by dividing by 2 //our adapter could encompass this functionality in here var charingPower:Int = charger.chargingPower / 2 var newBritishCharger = new BritishCharger() newBritishCharger.chargingPower = theAmericanCharger.chargingPower/2 return newBritishCharger } }
No mundo da programação, a diferença nos soquetes é análoga à diferença nos métodos internos usados para carregar. Os carregadores com métodos diferentes impossibilitariam o uso dos carregadores.
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger()
Tentar chamar o método charge() em myBritishDevice com americanChargerIFound não funcionaria, pois o AmericanDevice aceita apenas um AmericanCharger
Então é impossível fazer isso
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() myBritishDevice.charge(americanChargerIFound)
Neste cenário, o adaptador que criamos
AmericanToBritishChargerAdapter agora pode ser útil. Podemos usar o método returnNewCharger() para criar um novo BritishCharger, que podemos usar para cobrar. Tudo o que precisamos é criar uma instância do nosso adaptador e alimentá-lo com o AmericanCharger que temos, e ele criará um BritishCharger que podemos usar
var myBritishDevice = new BritishDevice() var americanChargerIFound = new AmericanCharger() //We create the adapter and feed it the americanCharger var myAdapter = AmericanToBritishChargerAdapter(theAmericanCharger) //calling returnNewCharger from myAdapter would return a BritishCharger var britishChargerFromAdapter = myAdapter.returnNewCharger() //and once we have the britishCharger we can now use it myBritishDevice.charge(britishChargerFromAdapter)
RecyclerView.LayoutManager
Ao lidar com um ViewGroup teríamos Views colocadas dentro dele. É o LayoutManager que teria a tarefa de descrever como as Views são dispostas internamente.
Para fins de comparação, ao trabalhar com Linearlayout ViewGroup, o caso de uso que desejamos é a capacidade de colocar os itens verticalmente ou horizontalmente. Isso é facilmente implementado adicionando um atributo de orientação, que nos informa como o layout linear será colocado na tela. Podemos fazer isso usando android:orientation=VERTICAL|HORIZONTAL
atributo.
Também temos outro ViewGroup chamado GridLayout, seu caso de uso é quando queremos colocar Views em uma estrutura de grade retangular. Isso pode ocorrer por motivos como facilitar o consumo dos dados que apresentamos ao usuário do aplicativo. Por design, o GridLayout permite configurações para ajudá-lo a atingir esse objetivo, tendo configurações onde podemos definir as dimensões da grade, por exemplo, podemos ter uma grade 4×4, uma grade 3 x 2.
RecyclerView.ViewHolder
O ViewHolder é uma classe abstrata que também estendemos do RecyclerView. O ViewHolder nos fornece métodos comuns para nos ajudar a referenciar uma View que colocamos no RecyclerView, mesmo depois que o maquinário de reciclagem no RecyclerView mudou várias referências que não conhecemos.
Listas grandes
RecyclerViews são usados quando queremos apresentar um conjunto realmente grande de Views ao usuário, sem esgotar nossa capacidade. RAM em nosso dispositivo para cada instância da Visualização criada.
Se tomássemos o caso de uma Lista de Contatos, teríamos uma ideia geral de como seria a aparência de um contato na lista. O que faríamos então é criar um layout de modelo – que na verdade é uma Visualização – com slots onde vários dados de nossa lista de contatos serão preenchidos. A seguir está um pseudocódigo que explica todo o propósito:
//OneContactView <OneContact> <TextView>{{PlaceHolderForName}}</TextView> <TextView>{{PlaceHolderForAddress}}</TextView> <ImageView>{{PlaceHolderForProfilePicture}}</ImageView> <TextView>{{PlaceHolderForPhoneNumber}}</TextView> </OneContact>
Teríamos então uma ContactList desta natureza
<ContactList> </ContactList>
Se for o caso, se estivéssemos codificando o conteúdo, não teríamos uma forma programática de adicionar novo conteúdo à lista sem reescrever o aplicativo. Felizmente para nós. Adicionar uma View a um ViewGroup é suportado por um addView(view:View)
método.
Mesmo que seja esse o caso, não é assim que o RecyclerView adiciona visualizações filhas a ele.
Em nosso caso de uso, teríamos uma longa lista de contatos. Para cada contato da lista, precisaríamos criar o OneContactView e preencher os dados dentro da View para corresponder aos campos da nossa classe Contact. Assim que tivermos a visualização, precisaremos adicioná-la ao RecyclerView para mostrar a lista.
data class Contact(var name:String, var address:String, var pic:String, var phoneNumber:Int) var contact1 = Contact("Guru","Guru97", "SomePic1.jpg", 991) var contact2 = Contact("Guru","Guru98", "SomePic2.jpg", 992) var contact3 = Contact("Guru","Guru99", "SomePic3.jpg", 993) var myContacts:ArrayList<Contact> = arrayListOf<Contact>(contact1,contact2,contact3)
Temos uma série de contatos chamada OneContactView. Ele contém slots para pegar o conteúdo da classe Contact e exibi-lo. No RecyclerView, temos que adicionar Views para que ele possa nos ajudar com sua capacidade de reciclagem.
O RecyclerView realmente não nos permite adicionar visualizações, mas permite adicionar um ViewHolder. Então, neste cenário, temos duas coisas que queremos conectar, mas não combinam. É aqui que entra o nosso adaptador. O RecyclerView nos fornece um adaptador muito parecido com o nosso AmericanToBritishChargerAdapter()
anterior, que nos permitiu converter nosso AmericanCharger, que era inutilizável com nosso BritishDevice, em algo utilizável, semelhante a um adaptador de energia na vida real.
Nesse cenário, o adaptador pegaria nosso array de Contatos e nossa Visualização e, a partir daí, geraria ViewHolders que o RecyclerView está disposto a aceitar.
O RecyclerView fornece uma interface que podemos estender para criar nosso adaptador por meio da classe RecyclerView.Adapter. Dentro deste adaptador há uma forma de criar a classe ViewHolder com a qual o RecyclerView deseja trabalhar. Então, o que temos é a mesma situação de antes, mas com uma coisa a mais, que é o Adaptador.
Temos uma variedade de Contatos, uma visualização para exibir um contato OneContactView. Um RecyclerView é uma lista de Views que fornecem serviços de reciclagem, mas só estão dispostos a aceitar ViewHolders
Mas neste cenário, agora temos a classe RecyclerView.Adapter, que possui um método para criar ViewHolders dentro.
fun createViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder
O RecyclerView.ViewHolder é uma classe abstrata que pega nossa View como argumento e a converte em ViewHolder.
Ele usa o padrão wrapper usado para estender as habilidades das classes.
Base #2 – Padrão de wrapper
Usaremos um exemplo simples para demonstrar como podemos fazer os Animais falarem.
sealed trait Animal{ fun sound():String } data class Cat(name:String):Animal{ fun sound(){ "Meow" } } data class Dog(name:String):Animal{ fun sound(){ "Woof" } } var cat1 = Cat("Tubby") var dog1 = Dog("Scooby") cat1.sound() //meow dog1.sound() //woof
No exemplo acima, temos dois animais. Se por acaso quiséssemos adicionar um método para fazer falar, mas o autor da biblioteca não fosse divertido, ainda poderíamos encontrar uma maneira. O que precisamos é de um invólucro para nossa classe Animal. Faríamos isso tomando Animal como construtor para nossa classe
class SpeechPoweredAnimalByWrapper(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
Agora podemos passar uma instância de animal para SpeechPoweredAnimalByWrapper. Chamar o método sound() nele chamaria o método animal sound() passado. Também temos um método speak() adicional, que conta como uma nova funcionalidade que adicionamos aos animais transmitidos.
var cat1 = Cat("Garfield") cat1.sound()//"meow" cat1.speak()// doesn't work as it isn't implemented var talkingCat = new SpeechPoweredAnimalByWrapper(cat1) talkingCat.sound() //"meow" the sound method calls the one defined for cat1 talkingCat.speak() //"Hello, my name is Garfield"
Usando esse padrão, podemos fazer aulas e adicionar funcionalidades. Tudo o que precisaríamos é passar uma instância de classe e novos métodos definidos por nossa classe de empacotamento.
No nosso caso acima, usamos uma classe concreta. Também é possível implementar o mesmo em uma classe Abstract. O que precisaríamos fazer é adicionar a alteração da classe SpeechPoweredAnimalByWrapper para abstrata e pronto. Alteraremos o nome da classe para algo mais curto para torná-lo mais legível.
abstract class SpeechPowered(var myAnimal:Animal){ fun sound(){ myAnimal.sound() } speak(){ println("Hello, my name is ${myAnimal.name}") } }
É o mesmo de antes, mas significaria outra coisa. Em uma classe normal, podemos ter uma instância de uma classe da mesma forma que criamos cat1 e dog1. As classes abstratas, entretanto, não foram feitas para serem instanciadas, mas sim para estender outras classes. Então, como usaríamos a nova classe abstrata SpeechPowered(var myAnimal:Animal). Podemos utilizá-lo criando novas classes que irão estendê-lo e, por sua vez, ganhar sua funcionalidade.
Em nosso exemplo, criaremos uma classe SpeechPoweredAnimal que estende a classe
class SpeechPoweredAnimal(var myAnimal:Animal):SpeechPowered(myAnimal)
var cat1 = Cat("Tubby") var speakingKitty = SpeechPoweredAnimal(cat1) speakingKitty.speak() //"Hello, my name is Tubby"
Este é o mesmo padrão usado no ViewHolder. A classe RecyclerView.ViewHolder é uma classe abstrata que adiciona funcionalidade à View, da mesma forma que adicionamos o método speak aos animais. A funcionalidade adicionada é o que o faz funcionar ao lidar com o RecyclerView.
É assim que criaríamos um OneContactViewHolder a partir do OneContactView
//The View argument we pass is converted to a ViewHolder which uses the View to give it more abilities and in turn work with the RecyclerView class OneContactViewHolder(ourContactView: View) : RecyclerView.ViewHolder(ourContactView)
RecyclerView possui um adaptador que nos permite conectar nosso array Contacts ao ContactsView com o RecyclerView
Adicionando uma visualização
O ViewGroup não redesenha o ViewGroup automaticamente, mas segue uma programação específica. Pode ser que no seu dispositivo ele redesenhe a cada 10ms ou 100ms, ou se escolhermos um número absurdo, digamos 1 minuto, quando adicionamos uma View a um ViewGroup, você verá as alterações 1 minuto depois, quando o ViewGroup “atualizar”.
RecyclerView.Recycler
Trabalho de base #3. Cache
Um dos melhores exemplos de onde fazemos atualizações regularmente é no navegador. Vamos imaginar, por exemplo, que o site que estamos visitando seja estático e não envie conteúdo de forma dinâmica, precisaríamos continuar atualizando para ver as alterações.
Para este exemplo, vamos imaginar que o site em questão seja o Twitter. Teríamos uma série de tweets estáticos listados e a única maneira de ver novos tweets seria clicando no botão Atualizar para recuperar o conteúdo.
Repintar a tela inteira é obviamente algo caro. Se imaginarmos que fosse esse o caso, teríamos largura de banda limitada com nossa operadora de telefonia. E nossa lista de tweets tinha muitas imagens e vídeos, seria caro baixar novamente todo o conteúdo da página a cada atualização.
Precisaríamos de uma maneira de armazenar os Tweets já carregados e garantir que nossa próxima solicitação tenha a capacidade de dizer os Tweets que já possui. Portanto, ele não baixa tudo novamente e apenas obtém os novos Tweets que possui e também verifica se algum Tweet que foi salvo localmente não está mais lá para poder excluí-lo localmente. O que estamos descrevendo é chamado de cache.
A informação que enviamos ao site sobre os conteúdos que possuímos chama-se metadados. Então, na verdade, não estamos apenas dizendo “queremos carregar o seu site”, dizemos “queremos carregar o seu site, e aqui estão alguns dos conteúdos que já havíamos salvo da última vez que carregamos, por favor, use-os para envie-nos apenas o que não está lá, para não usarmos muita largura de banda, pois não temos muitos recursos.”
Chamadas de layout – A lista de tweets deve ser uma loucura
Um exemplo de chamada de layout é scrollToPosition
Este é um exemplo comum que está presente em aplicativos de bate-papo. Se alguém em um tópico de bate-papo responder a um balão de bate-papo anterior, alguns aplicativos de bate-papo incluem a resposta e um link para o balão de bate-papo, que, ao clicar, direciona você para onde estava sua mensagem original.
No caso, chamamos esse método antes de termos um LayoutManager adicionado ao nosso RecyclerView e antes de termos um RecyclerView.Adapter, o scrollToPosition(n:Int) é simplesmente ignorado.
Comunicação entre componentes do RecyclerView
Trabalho de base #4. Retornos de chamada
O RecyclerView, ao realizar seu trabalho, possui muitas partes móveis. Tem que lidar com o LayoutManager que nos diz como organizar as Views, seja Linearmente ou em Grid. Ele tem que lidar com um Adapter que faz o trabalho de converter nossos itens contactList em Views OneContactView e depois em ViewHolders OneContactViewHolder que o RecyclerView está disposto a trabalhar dentro dos métodos que ele nos fornece.
A matéria-prima para o RecyclerView são nossas visualizações, por exemplo, OneContactView e fonte de dados.
contactList:Array<Contact>
Usamos um cenário simples como ponto de partida para ter uma ideia do que o RecyclerView está tentando alcançar.
Um caso básico de quando teríamos uma matriz estática de 1000 contatos que queremos mostrar ao usuário é fácil de entender.
O mecanismo do RecyclerView realmente começa a ganhar vida quando a lista não é mais estática.
Com uma lista dinâmica, temos que pensar no que acontece com a Visualização na tela quando adicionamos um item à lista ou removemos um item da lista.
RecyclerView.LayoutManager
Além de decidir como nossas visualizações serão dispostas de forma linear ou em grade. O LayoutManager faz muito trabalho interno para ajudar o Reciclador a saber quando fazer a reciclagem.
É responsável por acompanhar as Views atualmente visíveis na Tela e comunicar essas informações ao mecanismo de Reciclagem. À medida que o usuário rola para baixo, o gerenciador de Layout é responsável por informar ao sistema de Reciclagem as Views que saem de foco no topo para que possam ser reaproveitadas ao invés de permanecerem lá e consumirem memória ou em vez de destruí-las e ter que criar novos.
O que isso significa é que o LayoutManager precisa acompanhar onde o usuário está enquanto ele rola nossa lista. Ele faz isso tendo uma lista de posições que são base de índice, ou seja, o primeiro item é começar em 0 e aumentar para corresponder ao número de itens da nossa lista.
Se pudermos visualizar 10 itens em nossa lista de, digamos, 100, no início, o LayoutManager estará ciente de que tem em foco a visualização-0 até a visualização-9. À medida que rolamos, o LayoutManager é capaz de calcular as visualizações que saem de foco.
O LayoutManager é capaz de liberar essas visualizações para o mecanismo de reciclagem para que possam ser reutilizadas (novos dados podem ser vinculados a elas, por exemplo, os dados de contato de uma visualização podem ser removidos e novos dados de contato do próximo segmento podem substituir os espaços reservados).
Este seria um caso feliz se a lista que temos fosse estática, mas um dos casos de uso mais comuns de uso de um RecyclerView é com listas dinâmicas onde os dados podem vir de um endpoint online ou até mesmo de um Sensor. Não apenas os dados são adicionados, mas os dados da nossa Lista às vezes são removidos ou atualizados.
O estado dinâmico dos nossos dados pode dificultar muito o raciocínio sobre o LayoutManager. Por isso, o LayoutManager mantém uma lista própria de itens e posições, separada da Lista que o componente Reciclagem utiliza. Isso garante que o trabalho de layout seja executado corretamente.
Ao mesmo tempo, o LayoutManager do RecyclerView não deseja deturpar os dados que possui. Para funcionar corretamente, o LayoutManager sincroniza com o RecyclerView.Adapter em determinados intervalos (60ms) compartilhando informações sobre nossos itens da Lista. ou seja, itens adicionados, atualizados, removidos, movidos de uma posição para outra). O LayoutManager, ao receber essas informações, reorganiza o conteúdo da Tela para corresponder às alterações quando necessário.
Muitas das operações principais que lidam com RecylerView giram em torno da comunicação entre RecyclerView.LayoutManager e RecyclerView.Adapter que armazena nossas listas de dados às vezes estáticos ou às vezes dinâmicos.
Mais do que isso, RecyclerView nos apresenta métodos que podemos usar para ouvir eventos como onBindViewHolder quando nosso RecyclerView.Adapter vincula o conteúdo de nossa lista, por exemplo, um contato a um ViewHolder para que agora ele seja usado para exibir as informações na tela.
Outro é onCreateViewHolder, que nos informa quando o RecyclerView. O adaptador pega uma View regular como OneContactView e a converte em um item ViewHolder com o qual o RecyclerView pode trabalhar. Do nosso ViewHolder para uso pelo RecyclerView. onViewDetached
Além dos principais mecanismos que permitem a reciclagem. O RecyclerView fornece maneiras de personalizar o comportamento sem afetar a Reciclagem.
A reutilização de visualizações dificulta a realização de coisas comuns que estamos acostumados a fazer com visualizações estáticas, como reagir a eventos onClick.
Como estamos cientes de que RecyclerView.LayoutManager
que apresenta as Views ao usuário pode, por um momento, ter uma lista de itens diferente da RecyclerView.Adapter
que contém a lista que armazenamos em um banco de dados ou transmitindo de uma fonte. Colocar eventos OnClick diretamente nas Views pode levar a um comportamento inesperado, como excluir ou alterar o contato errado.
Gradle
Se quisermos usar o RecyclerView, precisamos adicioná-lo como uma dependência em nosso arquivo build .gradle.
No exemplo a seguir, usamos a implementação “androidx.recyclerview:recyclerview:1.1.0”, que é a versão mais atual de acordo com este artigo.
Depois de adicionar a dependência ao nosso Gradle arquivo, seremos solicitados por Android Estúdio para Synccronizar as mudanças,
É assim que nosso Gradle O arquivo terá a aparência após adicionar um RecyclerView em um projeto vazio apenas com os padrões.
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig { applicationId "com.guru99.learnrecycler" minSdkVersion 17 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.core:core-ktx:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation "androidx.recyclerview:recyclerview:1.1.0" }
Temos apenas um arquivo de layout no momento. Começaremos com um exemplo simples onde usaremos um RecyclerView para mostrar uma lista de nomes de frutas na tela.
Lista de itens
Navegaremos até nosso arquivo MainActivity e criaremos um array com nomes de frutas logo antes do método onCreate() que foi gerado durante a configuração.
package com.guru99.learnrecycler import androidx.appcompat.app.AppCompatActivity import android.os.Bundle class MainActivity : AppCompatActivity() { var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
Nosso próximo objetivo será apresentar esta lista na tela utilizando um RecyclerView.
Para fazer isso, navegaremos até o diretório de layout que contém nossos Layouts e criaremos uma View que será responsável por mostrar uma fruta.
Layout a ser usado para cada item da nossa lista
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/fruitName" /> </TextView>
No TextView acima, adicionamos um campo id que será usado para identificar uma View.
Não é gerado por padrão. Temos em nosso TextView o id frutaName para corresponder aos dados, que serão vinculados a ele.
Adicionando o RecyclerView ao layout principal
Na mesma atividade, há o arquivo de layout main_layout.xml que foi gerado para nós por padrão.
Se escolhermos um projeto vazio. Terá gerado um XML contendo um ConstraintLayout e dentro dele estará um TextView com o texto “Hello”.
Excluiremos todo o conteúdo e faremos com que o layout contenha apenas o RecyclerView conforme abaixo:
<?xml version="1.0" encoding="utf-8"?> <androidx.recyclerview.widget.RecyclerView 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:id="@+id/fruitRecyclerView" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" />
Também adicionamos um atributo id para o RecyclerView que usaremos para referenciá-lo em nosso código.
android:id="@+id/fruitRecyclerView"
Em seguida, navegaremos de volta ao nosso arquivo MainActivity. Usando os IDs que criamos, poderemos referenciar as Views que acabamos de criar.
Começaremos referenciando o RecyclerView usando o método findViewById() fornecido por Android. Faremos isso em nosso método onCreate().
Nosso método onCreate() terá a seguinte aparência.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) }
Crie um ViewHolder
A seguir, criaremos um RecyclerView.ViewHolder que é responsável por pegar nossa View e convertê-la em um ViewHolder, que o RecyclerView usa para exibir nossos itens.
Faremos isso logo após nosso divertido método onCreate().
package com.guru99.learnrecycler import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.View import androidx.recyclerview.widget.RecyclerView class MainActivity : AppCompatActivity() { var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) } class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView) }
Crie um RecyclerViewAdapter
A seguir, criaremos uma classe FruitArrayAdapter que estende a classe RecyclerView.Adapter.
O FruitArrayAdapter que criamos será responsável por fazer o seguinte.
Receberá nomes de frutas do array de frutas. Ele criará um ViewHolder usando nossa visualização one_fruit_view.xml. Em seguida, vinculará a fruta a um ViewHolder e vinculará dinamicamente o conteúdo à visualização que criamos one_fruit_view.xml.
package com.guru99.learnrecycler import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.View import androidx.recyclerview.widget.RecyclerView class MainActivity : AppCompatActivity() { var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) } class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView) class FruitArrayAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() }
Android O Studio adicionará rabiscos vermelhos em nosso FruitArrayAdapter, informando que precisamos implementar um método que o RecyclerView possa usar para conectar nosso array a um ViewHolder que o RecyclerView possa usar.
class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder { } override fun getItemCount(): Int { } override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { } }
Começaremos com a parte mais fácil do código gerado: o método getItemCount(). Sabemos como obter o número de itens em nosso array chamando o método array.length.
class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder { } override fun getItemCount(): Int { return fruitArray.size } override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { } }
Em seguida, implementaremos o método override fun onCreateViewHolder.
É aqui que o RecyclerView nos pede para ajudá-lo a construir um FruitHolder para ele.
Se nos lembrarmos, esta era a aparência da nossa classe FruitViewHolder:
class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView)
Requer nosso fruitView que criamos como um arquivo xml one_fruit_view.xml
Podemos criar uma referência para este xml e convertê-lo em uma View da seguinte maneira.
class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder { var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false) var fruitViewHolder = FruitViewHolder(fruitView) return fruitViewHolder } override fun getItemCount(): Int { return fruitArray.size } override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { } }
O bit restante é a substituição
fun onBindViewHolder(holder: FruitViewHolder, position: Int)
O RecyclerView.Adapter pergunta com um número inteiro de posição, que usaremos para buscar um item de nossa lista. Ele também nos fornece um suporte para que possamos vincular o item que obtemos do frutaArray à Visualização que está sendo mantida dentro do suporte da visualização.
A View que está contida em um ViewHoder é acessível através do campo ViewHolder.itemView. Assim que obtivermos a visualização, podemos usar o id frutaName que criamos anteriormente para definir o conteúdo.
override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName) var aFruitName = fruitArray.get(position) ourFruitTextView.setText(aFruitName) }
Com isso nosso FruitArrayAdapter está completo e tem a seguinte aparência.
class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder { var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false) var fruitViewHolder = FruitViewHolder(fruitView) return fruitViewHolder } override fun getItemCount(): Int { return fruitArray.size } override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName) var aFruitName = fruitArray.get(position) ourFruitTextView.setText(aFruitName) } }
Finalmente, estamos prontos para conectar as peças restantes do nosso RecyclerView. Que estão criando um LayoutManager, que informará ao RecyclerView como exibir o conteúdo da lista. Seja para mostrar de forma linear usando LinearLayoutManager ou em grade usando GridLayoutManager ou StaggeredGridLayoutManager.
Criar gerenciador de layout
Voltaremos à nossa função onCreate e adicionaremos o LayoutManager
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) var fruitLinearLayout = LinearLayoutManager(this) myFruitRecyclerView.layoutManager =fruitLinearLayout }
Conecte nosso adaptador aos itens e configure-o no RecyclerView
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) var fruitLinearLayout = LinearLayoutManager(this) myFruitRecyclerView.layoutManager =fruitLinearLayout var fruitListAdapter = FruitListAdapter(fruitNames) myFruitRecyclerView.adapter =fruitListAdapter }
Também criamos uma instância fruitListAdapter e alimentamos a ela o array de nomes de frutas.
E basicamente, terminamos.
O arquivo MainActivity.kt completo tem a seguinte aparência.
package com.guru99.learnrecycler import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView class MainActivity : AppCompatActivity() { var fruitNames:Array<String> = arrayOf<String>("Banana", "Mango", "Passion fruit", "Orange", "Grape") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var myFruitRecyclerView: RecyclerView = findViewById(R.id.fruitRecyclerView) var fruitLinearLayout = LinearLayoutManager(this) myFruitRecyclerView.layoutManager =fruitLinearLayout var fruitListAdapter = FruitListAdapter(fruitNames) myFruitRecyclerView.adapter =fruitListAdapter } class FruitViewHolder(fruitView: View): RecyclerView.ViewHolder(fruitView) class FruitListAdapter(var fruitArray: Array<String>) : RecyclerView.Adapter<FruitViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitViewHolder { var fruitView = LayoutInflater.from(parent.context).inflate(R.layout.one_fruit_view, parent, false) var fruitViewHolder = FruitViewHolder(fruitView) return fruitViewHolder } override fun getItemCount(): Int { return fruitArray.size } override fun onBindViewHolder(holder: FruitViewHolder, position: Int) { var ourFruitTextView = holder.itemView.findViewById<TextView>(R.id.fruitName) var aFruitName = fruitArray.get(position) ourFruitTextView.setText(aFruitName) } } }