Introdução

Um desenvolvedor Android, cedo ou tarde, precisará utilizar listas dentro de algum dos fluxos do app que está desenvolvendo. Essas listas, até algum tempo atrás, eram implementadas utilizando ListViews (que eram terríveis em performance!), porém essa realidade começou a mudar com a introdução das RecyclerViews.

A RecyclerView, como o próprio nome diz, realiza um tipo de reciclagem nos componentes. Ou seja, diferente da ListView, onde cada novo item criado possuí uma view associada, a RecyclerView reaproveita views previamente criadas, assim aumentando a performance do aplicativo.

Apesar de suas vantagens, criar uma RecyclerView (a partir desse ponto, usarei apenas o termo Recycler) pode ser um processo tedioso e muitas vezes trabalhoso. Para usar a Recycler, é preciso criar um Adapter, ViewHolder, LayoutManager, entre outras parafernálias que resultam em "boilerplate code", então para evitar esse tipo de problema, usamos a biblioteca Groupie1!

A Groupie permite que criemos Recyclers complexas com menos esforço, usando uma abordagem de itens para criar cada uma das views. Nesse texto, a Groupie está na versão 2.8.0 (estável), ou seja, se algo não estiver funcionando, certifique-se de voltar para a versão 2.8.0 e estudar as mudanças!


Conhecendo a RecyclerView

É bem provável que, se você está lendo esse texto, já saiba o que é e como utilizar uma Recycler, porém vamos relembrar alguns pontos importantes! A RecyclerView é uma ViewGroup, que precisa de alguns componentes específicos para funcionar, sendo eles: RecyclerView, LayoutManager, Adapter e um ViewHolder. A ideia é que teremos uma fonte de dados e a Recycler deve transformar esses dados em Views, possibilitando a interação com o usuário.

  1. RecyclerView: Normalmente fica localizado dentro da Activity/Fragment, e é o componente de layout. Assim como um botão, devemos coloca-la dentro do xml da Activity/Fragment;
  2. LayoutManager: Responsável por gerenciar a Recycler. É nele que definimos, por exemplo, sentido (horizontal/vertical), formato (listagem, grid, etc), enfim, definições do comportamento do componente;
  3. Adapter: Responsável por "traduzir" o dado bruto para a View que será exibida para o usuário. Também é aqui que são definidos se um item será, ou não, exibido, além dos toques dos botões e outros tipos de interação;
  4. ViewHolder: É a referência da View que será utilizada. Normalmente utilizado dentro do Adapter para que a "tradução" seja realizada da maneira correta.
Imagem 1 - Funcionamento da RecyclerView

Groupie

Como dito anteriormente, a Groupie é uma biblioteca que auxilia na criação / utilização das RecyclerViews. Nela, toda novo componente é tratado como um Item, e cada item tem um layout e comportamento próprio, cabendo ao Adapter organizar a disposição desses na tela.

Falar é fácil, agora me mostre o código!

Uma vez que todas as dependências foram adicionadas e a RecyclerView devidamente colocada no XML da Activity/Fragment, é hora de criar o Adapter que será utilizado.

Como sabemos, o Adapter deve conter um ViewHolder, que será o responsável pelo binding entre dado e View, mas ao invés de escrever um punhado de código chato, basta o Adapter e os Itens usados descenderem de GroupieViewHolder, como pode ser visto no Código 1

class NumbersAdapter : GroupAdapter<GroupieViewHolder>() {
    fun setData(numberList: List<Int>) {
        numberList.forEach {
            add(NumberItem(number = it))
        }
    }

    private inner class NumberItem(private val number: Int) : Item<GroupieViewHolder>(){
        override fun getLayout(): Int = R.layout.item_layout

        override fun bind(viewHolder: GroupieViewHolder, position: Int) {
            with(viewHolder.itemView) {
                itemNumber.text = number.toString()
            }
        }

    }
}
Código 1 - Adapter

No Código 1, o Item criado é o NumberItem, que tem como parâmetro um número inteiro, e como View um TextView. É nele que ocorre a "tradução" do dado recebido para uma View. Além disso, também foi criado um método para "trazer" os dados oriundos do Dataset para o Adapter. E é isso, o Adapter está funcionando e pronto para ser utilizado pela Activity/Fragment.

class MainActivity : AppCompatActivity() {
    private lateinit var numbersAdapter: NumbersAdapter
    private val numbersList: List<Int> =
            listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18)

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

        numbersAdapter = NumbersAdapter()
        numberRecyclerView.layoutManager = GridLayoutManager(this, 2)
        numberRecyclerView.adapter = numbersAdapter
        numbersAdapter.setData(numbersList)

    }
}
Código 2 - MainActivity

O Código 2 ilustra como o Adapter deve ser instanciado e configurado dentro da Activity/Fragment. Uma vez que as configurações do LayoutManager, que é um GridLayoutManager com duas colunas, estão prontas, passamos o Dataset para a função setData.

Untitled.gif
Imagem 2 - Funcionamento

O resultado final pode ser visto na Imagem 2, onde uma RecyclerView totalmente funcional, em forma de Grid com 2 colunas, foi criada em poucos passos, e com pouquíssimo boilerplate code.


Itens Expansíveis

Já pensou em fazer uma lista onde os itens podem expandir e colapsar? Já imaginou fazer tudo isso sem ter um trabalho ABSURDO com o Adapter da RecyclerView? Se sim, a Groupie é a solução para todos os seus problemas. Dentro dos muitos itens disponíveis na biblioteca, apenas o ExpandableGroup permite que criemos Itens expansíveis. Para isso, na função de setData, ao invés de adicionar o NumberItem diretamente, devemos adiciona-lo a partir do ExpandableNumberItem.

fun setData(numberList: List<Int>) {
    numberList.forEach {
        add(ExpandableGroup(ExpandableNumberItem(number = it), false).apply {
            add(NumberItem(number = it*20))
        })
    }
}
Código 3 - Adição de Item Expansível

O ExpandableGroup espera um Item expansível e um booleano que representa o estado inicial do item. Além disso, um apply é realizado para adicionar os itens que estarão visíveis assim que o item for expandido.

private inner class ExpandableNumberItem(private val number: Int) : Item<GroupieViewHolder>(),
    ExpandableItem {
    override fun getLayout(): Int = R.layout.expandable_item_layout
    private lateinit var expandableGroup: ExpandableGroup

    override fun bind(viewHolder: GroupieViewHolder, position: Int) {
        with(viewHolder.itemView) {
            expandedImageView.setImageResource(getIcon())

            itemNumber.text = number.toString()
            numberItemLayout.clicks().doOnNext {
                expandableGroup.onToggleExpanded()
                expandedImageView.setImageResource(getIcon())
            }.subscribe()
        }
    }

    override fun setExpandableGroup(onToggleListener: ExpandableGroup) {
        expandableGroup = onToggleListener
    }

    private fun getIcon()= if (expandableGroup.isExpanded) R.drawable.minus else R.drawable.plus
}
Código 4 - ExpandableNumberItem

O item expansível criado é o ExpandableNumberItem, que estende de Item e ExpandableItem. Para que tenhamos acesso aos eventos de toque do item, é necessário que exista um ExpandableGroup DENTRO do ExapandableItem (vide linha 4, código 4), é ele quem possibilita a atualização do estado do item expansível.

expanded.gif
Imagem 3 - Itens expansíveis

Conclusão

A RecyclerView é um componente muito importante para quem desenvolve em Android, sendo utilizada em diversos casos, e de diversas formas. Por ser um componente tão importante e versátil, é interessante que o desenvolvedor saiba como funciona sua criação, utilização, e funcionamento. Então, para auxiliar na utilização desse componente, é possível utilizar bibliotecas como a Groupie, com o intuito de diminuir o número de linhas de código, e ao mesmo tempo aumentar a produtividade.

Todos os códigos exemplo utilizados nesse texto podem ser encontrados aqui. Existem duas branches com código, master e normal-item, na primeira está o projeto com itens expansíveis, na segunda, o com itens normais.


Referências

1 https://github.com/lisawray/groupie