Listas com RecyclerView

English version here

É muito comum aplicativos terem listas para apresentarem seu conteúdo de forma eficiente. E se mal implementada pelo desenvolvedor pode trazer descontentamento para o usuário, o seu cliente final. Pensando nisso o Android nos deu um componente poderoso chamado RecyclerView.


Sobre o RecyclerView

Avise seu chefe que agora você vai reciclar views

RecyclerView é uma “evolução” da ListView e da GridView, componentes presentes desde da primeira versão do Android para se fazer listas e grades de conteúdo.

Suas principais vantagens são:

  • Atualizações (melhorias, novas funcionalidades, correções e etc)
    O RecyclerView é atualizada frequentemente independente da versão do Android, pois ela faz parte da Biblioteca de Suporte v7 e tem recebido grande atenção dos desenvolvedores do Google desde seu lançamento, focado em constantes ajustes que melhoram continuamente a performance. Já a ListView e a GridView estão presentes direto na SDK do Android. Sendo assim só há atualizações para estes componentes quando a versão do Android é atualizada, o que não é tão frequente quanto a Biblioteca de Suporte v7.
  • Performance! Reuso da célula (view) ao descer/subir a lista
    Como o nome do componente sugere (Recycler é Reciclar em inglês) quando o usuário descer/subir a lista o componente identifica as views que não estão mais visíveis para o usuário e as reutiliza colocando novos valores, de acordo com o conteúdo daquela posição da lista. Fazendo isso evita-se criar novas views para cada novo conteúdo.
    Este design-patterns se chama Object Pool onde o objetivo é reduzir o tempo e custo das instanciações, reaproveitar objetos, melhorar a performance e o controle sobre os recursos. Outro design-patterns utilizado é o View Holder que tem como objetivo manter as referências da view ajudando a reciclagem. Assim, não é necessário procurar as referências da view quando for apresentar uma nova view para o usuário.
    Muitos desenvolvedores já aplicavam estes conceitos na ListView, mas no RecyclerView ele é obrigatório e facilitado, visando sempre um melhor desempenho.
  • Animações
    O RecyclerView por padrão utiliza a DefaultItemAnimator, classe responsável por animar os conteúdos da lista quando forem adicionados, removidos ou alterado sua posição, dando um feedback melhor para o usuário. O desenvolvedor poderá também definir sua própria animação se assim desejar. 
    Na ListView e GridView a animação sempre deverá ser implementada caso o desenvolvedor desejar, começando do zero.
  • Gerenciador de layout
    No RecyclerView existe um “gerenciador de layout” que define qual será a posição dos itens na lista (se será uma lista horizontal, vertical, uma grade e etc). Com essa flexibilidade podemos mudar a disposição dos itens de acordo com a configuração do usuário sem a necessidade de recriar toda a estrutura do RecyclerView em tempo de execução. 
    O ListView só suporta lista vertical, e caso o usuário queira mudar a disposição dos itens para uma grade o desenvolvedor teria que criar uma GridView e lidar com os ajustes necessário novamente.
  • Atualização apenas de itens alterados
    Na ListView quando era necessário atualizar a lista porque um item era inserido, modificado ou removido a API só permitia a atualização da lista completa ou era necessário muito código para que se atualizasse apenas o conteúdo desejado. No RecyclerView a API é otimizada para este cenário e prevê essa necessidade focada em melhorar a performance e disponibiliza meios para atualizarmos apenas o conteúdo em questão.

Componentes no RecyclerView

Exemplo da estrutura de uma lista

RecyclerView é apenas o ViewGroup onde irá conter a lista, para utilizá-lo é necessário alguns componentes a mais. Segue uma breve descrição abaixo:

  • RecyclerView
    Componente visual que ficará na Activity/Fragment e irá posicionar a lista na tela do usuário, assim como um campo de texto ou botão, por exemplo.
  • LayoutManager
    É o gerenciador de conteúdo descrito acima. Nele é definido qual é a disposição do itens da lista (se será uma lista vertical ou horizontal, por exemplo).
  • Adapter
    Classe responsável por associar a lista de conteúdo/objeto à view correspondente. Onde cada objeto da lista será um item na lista. É no Adapter onde se define se um item será exibido ou não.
  • ViewHolder
    É a referência para a view que é a parte visual de cada item da lista, que será replicada para todos elementos (na estrutura acima, ficaria dentro do Adapter).

Falar é fácil, me mostre código

Me mostre o código ou não aconteceu

Para utilizar RecyclerView é necessário colocar a dependência no arquivo build.gradle do módulo do aplicativo (geralmente o que fica dentro da pasta app).

Utilizando a versão 25.1.0 da RecyclerView, CardView e biblioteca de Desing
As bibliotecas de CardView e de Desing não são obrigatórias para a criação de listas, mas estão sendo usadas nos exemplos.
A versão usada é a 25.1.0 (a mais atual na época em que este artigo foi escrito) pois é a versão correspondente ao compileSdkVersion. Dê preferência a versão mais atualizada se possível, sempre.

Feito isso, o próximo passo é criar as listas.


Lista simples

Lista vertical

O primeiro passo é colocar o RecyclerView no layout da Activity.

Tela principal onde ficará a lista.

Além do RecyclerView a a tela terá um botão para incluir o conteúdo, neste caso, o FloatingActionButton. O resto do layout é igual à qualquer outro, sem segredos.

Um atributo interessante das listas no Android é o tools:listitem que foi colocado no componente do RecyclerView acima. 
Quando colocado com o layout do ViewHolder (no exemplo é o @layout/main_line_view), podemos ver no Preview do editor de layout do Android Studio como ficará nossa lista, basta colocar o seu layout.

Preview da lista no Android Studio

Também é preciso fazer o layout do ViewHolder, que será o layout usado em cada uma das linhas.

Layout correspondente à cada linha

Agora é hora de configurar nossa Activity.

Activity onde estará a lista.

Além de relacionarmos os componentes da view como de costume (fazendo findViewById(), por exemplo) é necessário configurar o LayoutManager e o Adapter.

Uma configuração extra adicionada é o addItemDecoration que recebe um DividerItemDecoration, usando o parâmetro VERTICAL. Com isso, é adicionado linhas divisórias entre os itens da lista.

Neste exemplo, foi escolhido o LinearLayoutManager como LayoutManager. Este tipo configura a lista como uma lista simples e vertical.

Muito se foi falado do LayoutManager, mas esta classe é apenas uma abstração. O que devemos utilizar mesmo são as classes abstratas, que será apresentado de acordo com cada exemplo.

Outro componente que deverá ser criado é o ViewHolder. O seu layout já foi criado acima então é preciso criar a classe Java para relacionar os seus campos, como fazemos em uma Activity para poder acessar os componentes visuais.

ViewHolder que irá relacionar componentes visuais do Layout ao código Java.

No exemplo, o Adapter está recebendo no construtor a lista de objetos, que no caso é UserModel. É sobre esta lista de objetos que é construído a lista visual.

Adapter da nossa lista simples.

Para a classe ser uma Adapter é necessário herdar RecyclerView.Adapter<ViewHolder> e implementar os métodos obrigatórios:

  • onCreateViewHolder(ViewGroup parent, int viewType)
    Método que deverá retornar layout criado pelo ViewHolder já inflado em uma view.
  • onBindViewHolder(ViewHolder holder, int position)
    Método que recebe o ViewHolder e a posição da lista. Aqui é recuperado o objeto da lista de Objetos pela posição e associado à ViewHolder. É onde a mágica acontece!
  • getItemCount()
    Método que deverá retornar quantos itens há na lista. Aconselha-se verificar se a lista não está nula como no exemplo, pois ao tentar recuperar a quantidade da lista nula pode gerar um erro em tempo de execução (NullPointerException).

Vale ressaltar que os métodos onCreateViewHolder e onBindViewHolder não são chamados para todos os itens inicialmente, eles são chamados apenas para os itens visíveis para o usuário. Quando o usuário sobe e desce a lista, estes dois métodos são chamados novamente associando a view reciclada ao conteúdo daquela posição que agora será visível.

Feito isto e está pronto. Temos uma lista!

Temos uma lista, de fato. Mas quando ela foi criada na Activity foi passado uma lista vazia no adapter. Agora é hora de popular, editar e remover itens.

Tenha em mente que Adapter irá trocar o conteúdo da views que irão se reciclando percorrendo esta lista de objetos. Portanto, para adicionar, atualizar ou remover itens da lista devemos trabalhar com a lista de objetos que foi passado no construtor e está dentro da classe do Adapter (List<UserModel> mUsers). Seu Adapter deverá ser capaz de cuidar desta reciclagem.
Neste artigo não será abordado códigos que não sejam relacionados ao RecyclerView. Por exemplo, técnicas e bibliotecas de terceiros que ajudaram no desenvolvimento do código.

Como descrito na apresentação será usado um botão para gerar conteúdo. Ao clicar neste botão é chamado um método público do Adapter para incluir novos itens na lista.

Inserir objetos na lista e atualizar o Adapter

Adicionar um novo item a lista e notificar que há mudanças.

Quando o usuário clicar no botão de adicionar será passado por parâmetro o novo Objeto (UserModel). 
Este novo objeto será adicionado na lista original do Adapter (linha 11) e depois é necessário “notificar” o Adapter que existe um novo item naquela posição (linha 12).

Como dito acima, esta é uma das grandes vantagens do RecyclerView onde é atualizado apenas a View do novo item da lista, ao invés de atualizar toda a lista para notificar que há uma view nova, como era feito no ListView.
Inserindo itens na lista.

Neste exemplo foi adicionado dois botões em cada view/linha. Um para incrementar o número de cada item da lista e outro para remover aquele elemento da lista.
As ações de click destes botões foram configuradas no método onBindViewHolder do Adapter, como exibido no Adapter acima.

Atualizar objetos na lista e atualizar o Adapter

Editando um objeto da lista e notificado que há mudanças

O código para atualizar um item é muito parecido com o código de incluir um item. Basta localizar o objeto na lista mUsers do Adapter e atualizar como desejar. Após isso, é necessário notificar o Adapter novamente que há uma atualização no item da posição indicada (linha 4).

Atualizando itens na lista

Remover objetos na lista e atualizar o Adapter

Removendo um item da lista.

Para excluir um item da lista é necessário remover ele da lista de objetos da classe (linha 3, removendo do mUsers) e notificar o Adapter que aquele item foi removido (linha 4).
Além disso, é necessário notificar os itens abaixo que há mudanças com eles (linha 5), pois ao remover um objeto do meio da lista é necessário avisar o Adapter que os objetos abaixo dele sofreram alteração na sua posição (index). Caso contrário pode acontecer o seguinte cenário: 
Imagine que uma lista tem 5 itens e o usuário removeu o segundo. Quando o usuário clicar no “novo” segundo item (que era o terceiro) ele irá interagir na verdade com o terceiro item. Isso porque o item removido sumiu e o Android animou o deslocamento dos items abaixo para cima, mas apenas visualmente. Por isso é importante sempre notificar o Adapter que estes itens abaixos mudaram sua posição.

Excluindo itens da lista

Lista horizontal

Itens alinhados lateralmente, onde deslizam para esquerda e direita

No RecyclerView é muito fácil mudar de uma lista vertical para uma lista horizontal, basta ajustar o LayoutManager na Activity.

Configurando LayoutManager para lista horizontal

Também é utilizado o LinearLayoutManager, porém com outro construtor passando o contexto, a configuração HORIZONTAL e um boolean definindo se a lista vai exibir os itens em ordem reversa ou não.

Caso desejar uma lista vertical com os itens na ordem reversa devemos usar o mesmo construtor, porém passando VERTICAL no segundo argumento.
Exemplo de lista horizontal

Outra componente que talvez seja necessário atualizar é o layout do ViewHolder, para que a view seja ajustada melhor na lista horizontal. No exemplo de lista verticais foi utilizado uma linha com texto e dois botões. Para o exemplo de lista horizontais está sendo utilizado o componente CardView e mais um componente de texto no centro, para uma melhor visualização.

Além do método onBindViewHolder do Adapter que foi alterado para associar mais um campo (texto verde em latim no centro da view), as demais configurações do Adapter e também as funções de inserir, alterar e excluir são exatamente iguais. E com poucas mudanças temos uma nova disposição de conteúdo.

One dream, one soul, one prize, one goal… 🎤

Grade

Conteúdo em Grade (Grid, em inglês)

Mais uma vez é utilizado a estrutura anterior alterando apenas o LayoutManager.

Configurando ReciclerView para usar grades com 2 colunas

Para a disposição do tipo grade foi utilizada a implementação GridLayoutManager do gerenciador de Layout. No construtor foi passado além do contexto, o número de colunas desejado na grade.

Exemplo de lista em grade

A implementação da Activity, Adapter, ViewHolder… você já sabe, não há diferenças.

Você pode sentir a “magica”?

Porém, as vezes o conteúdo/layout não é de um tamanho fixo para cada CardView e a grade pode ficar com lacunas e não aproveitar o máximo da tela do usuário. Por exemplo na imagem abaixo:

Grade com lacunas entre as views

Para casos como estes o Android tem um gerenciador especifico. E vamos à ele :).

Grade escalonável

Com o StaggeredGridLayoutManager a grade de conteúdo fica escalonável e o Android irá aproveitar o final de cada card de cada coluna para inciar a próxima view.

Utilizando grade escalonável

Com esta pequena mudança o Android aproveita mais a tela do usuário para exibir mais conteúdo!

Grade escalonável exibindo o máximo de conteúdo na tela

Dicas e boas práticas

Anota aí!
  • Tenha em mente que deve-se sempre trabalhar a lista de objetos e o Adapter deverá ser capaz de lidar com as mudanças. A lista irá subir/descer e o Adapter deve ser capaz de trabalhar os dados sozinho. Não faz sentido tentar acessar as views da lista através de outros lugares que não seja o Adapter.
  • Separe as responsabilidades de interface e de carregamento de dados. Não carregue dados na thread principal pois isso pode bloquear a tela do aplicativo por algum tempo, causando descontentamento para o usuário.
  • Caso o conteúdo da lista seja simples e sem ações não é aconselhável utilizar CardViews. Segundo a referência de design do Google, isso pode distrair o usuário. Você encontra a referência de CardView aqui e a de listas aqui.
  • Caso o aplicativo utilize várias listas e elas forem parecidas, considere utilizar o mesmo Adapter e ViewHolder se possível, evitando códigos duplicados.
  • Avalie o uso do RecyclerView. Como vimos não é tão prático criar a estrutura do RecyclerView. Caso precise de uma lista bem simples, sem cliques e sem interações considere ListView. Apesar de ser um componente ultrapassado ainda não está deprecated (termo usado quando algo já está obsoleto).

Considerações finais

Código de exemplo

O código utilizado no post encontra-se neste repositório do GitHub.

Documentação oficial

Na documentação do Android há uma parte falando sobre RecyclerView, você encontrará neste link. A documentação está neste link.

Contatos

Pelo e-mail orafaaraujo@gmail.com, pelo skype orafaaraujo, e pelo slack/medium/twitter @orafaaraujo. 
Feedbacks são bem-vindos!

Comunidade Android

Participe do maior fórum sobre Android no Brasil no slack Android Dev BR
Site: http://www.androiddevbr.org | Convite: http://slack.androiddevbr.org

Obrigado!