Android

10 fev, 2017

Listas com RecyclerView

Publicidade

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

Sobre o RecyclerView

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 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 tiverem sua posição alterada, 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 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).

apply plugin: 'com.android.application'

android {
   compileSdkVersion 25
   ...
}

dependencies {
    ...
    compile 'com.android.support:recyclerview-v7:25.1.0'
    compile 'com.android.support:cardview-v7:25.1.0'
    compile 'com.android.support:design:25.1.0'
    ...
}

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.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:tools="http://schemas.android.com/tools"
    ... />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view_layour_recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:listitem="@layout/main_line_view"/>

    <android.support.design.widget.FloatingActionButton
        ... />

</FrameLayout>

Além do RecyclerView, 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.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
    xmlns:tools="http://schemas.android.com/tools"
    ... >

    <TextView
        android:id="@+id/main_line_title"
        tools:text="Line 1"
        ... />

    <ImageButton
        android:id="@id/main_line_more"
        android:src="@drawable/ic_add_black_24dp"
        ... />

    <ImageButton
        android:id="@id/main_line_delete"
        android:src="@drawable/ic_delete_black_24dp" 
       ... />

</RelativeLayout>

Agora é hora de configurar nossa Activity.

public class LinearLayoutActivity extends AppCompatActivity {

    RecyclerView mRecyclerView;

    private LineAdapter mAdapter;
    
    ...

    private void setupRecycler() {

        // Configurando o gerenciador de layout para ser uma lista.
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(layoutManager);

        // Adiciona o adapter que irá anexar os objetos à lista.
        // Está sendo criado com lista vazia, pois será preenchida posteriormente.
        mAdapter = new LineAdapter(new ArrayList<>(0));
        mRecyclerView.setAdapter(mAdapter);

        // Configurando um dividr entre linhas, para uma melhor visualização.
        mRecyclerView.addItemDecoration(
                new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
    }
}

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, são adicionadas 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 foi falado do LayoutManager, mas esta classe é apenas uma abstração. O que devemos utilizar mesmo são as classes abstratas, que serão apresentadas 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.

public class LineHolder extends RecyclerView.ViewHolder {

    public TextView title;
    public ImageButton moreButton;
    public ImageButton deleteButton;

    public LineHolder(View itemView) {
        super(itemView);
        title = (TextView) itemView.findViewById(R.id.main_line_title);
        moreButton = (ImageButton) itemView.findViewById(R.id.main_line_more);
        deleteButton = (ImageButton) itemView.findViewById(R.id.main_line_delete);
    }
}

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.

public class LineAdapter extends RecyclerView.Adapter<LineHolder> {

    private final List<UserModel> mUsers;

    public LineAdapter(ArrayList users) {
        mUsers = users;
    }

    @Override
    public LineHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new LineHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.main_line_view, parent, false));
    }

    @Override
    public void onBindViewHolder(LineHolder holder, int position) {
        holder.title.setText(String.format(Locale.getDefault(), "%s, %d - %s",
                mUsers.get(position).getName(),
                mUsers.get(position).getAge(),
                mUsers.get(position).getCity()
        ));

        holder.moreButton.setOnClickListener(view -> updateItem(position));
        holder.deleteButton.setOnClickListener(view -> removerItem(position));
    }

    @Override
    public int getItemCount() {
        return mUsers != null ? mUsers.size() : 0;
    }
   
}

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

    /**
     * Método publico chamado para atualziar a lista.
     * @param user Novo objeto que será incluido na lista.
     */
    public void updateList(UserModel user) {
        insertItem(user);
    }

    // Método responsável por inserir um novo usuário na lista e notificar que há novos itens.
    private void insertItem(UserModel user) {
        mUsers.add(user);
        notifyItemInserted(getItemCount());
    }

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

    // Método responsável por atualizar um usuário já existente na lista.
    private void updateItem(int position) {
        UserModel userModel = mUsers.get(position);
        userModel.incrementAge();
        notifyItemChanged(position);
    }

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

    // Método responsável por remover um usuário da lista.
    private void removerItem(int position) {
        mUsers.remove(position);
        notifyItemRemoved(position);
        notifyItemRangeChanged(position, mUsers.size());
    }

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 abaixo 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 o gerenciador de layout para ser uma lista horizontal.
        LinearLayoutManager layoutManager
                = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
        mRecyclerView.setLayoutManager(layoutManager);

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

Outro 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.

        // Criando o GridLayoutManager com duas colunas, descritas no segundo argumento.
        GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
        mRecyclerView.setLayoutManager(layoutManager);

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, às 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 a 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.

        // Criando o LayoutManager com duas colunas, descritas no primeiro argumento
        // e no sentido vertical (como uma lista vertical).
        StaggeredGridLayoutManager layoutManager =
                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(layoutManager);

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 o 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 artigo encontra-se neste repositório do GitHub.

Documentação oficial: na documentação do Android há uma parte falando sobre RecyclerView, que 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!