Visualizações dinâmicas do Android com RecyclerView

Gustavo Santorio
Comunidade XP
4 min readApr 12, 2021

--

Photo by Mohammad Rahmani from Unsplash

Durante vários anos como desenvolvedor Android, tive sempre o RecyclerView como parceiro para muitas das funcionalidades que entreguei em todas as empresas para as quais trabalhei. Todavia, cada nova funcionalidade leva-me, frequentemente, a enfrentar um desafio mais difícil, o que me fez adquirir muita experiência e passar por alguns bugs também.

Um dos maiores desafios com que me deparei foi o de unir diferentes Views e ViewHolders na mesma Lista. Para que isto aconteça, é necessário implementar o método getItemViewType(…) e passar manualmente para o RecyclerView’s Adapter todos os Tipos Inteiros disponíveis. Lembrando que para compartilhar visualizações entre Screens e Adapters, é necessário colocar, manualmente, o ViewHolders neles.

Eu trouxe uma solução que resolve todos (ou quase todos) os problemas que temos enquanto lidamos com vários tipos de ViewHolders. Essa solução foi pensada e criada por mim e Rodrigo Vianna Calixto de Oliveira

Neste artigo, vou explicar a Estrutura Dinâmica para RecyclerViews.

Vamos falar de código

A primeira coisa sobre a qual precisamos falar é sobre como deixar os tipos de visualização dinâmicos, e a resposta é: o modelo!

Podemos criar um Modelo que nos diga o Tipo de visualização e como a moldar. O modelo tem uma estrutura de amostra com dois parâmetros (Key e Value) que dizem ao Adapter o tipo de visualização.

data class SimpleVO(val key: String, val value: Any?)

Key é um parâmetro String que representa o tipo de visualização, mas o getItemViewType(…) precisa devolver um Integer. Como então converter isto?

Para isto, é necessário criar uma classe Enum, que no cenário, vamos chamar de DynamicComponent. Esta classe tem todos os tipos de visualização e tudo o que devemos fazer é chamar a que tipo é a key que foi passada.

enum class DynamicComponent() {
FIRSTCOMPONENT,
SECONDCOMPONENT;companion object {
fun getDynamicComponentByName(name: String?): DynamicComponent? = name?.let {
return try
{
valueOf(it.toUpperCase(Locale.getDefault()))
} catch (ignored: Exception) {
null
}
}
}
}

No Adapter, só é preciso chamar este método e obter o valor ordinal do Enum:

override fun getItemViewType(position: Int): Int {
vos[position].let { simpleVO ->
DynamicComponent
.getDynamicComponentByName(simpleVO.key)
?.ordinal
?.let {
return it
}
}
return super
.getItemViewType(position)
}

E isto é tudo! Agora você tem um tipo de visualização dinâmica!

Tudo o que você precisa fazer agora é registar as suas keys na classe DynamicComponent, e depois criar os seus modelos com a sua key e parâmetros de value como este:

abstract class ViewRenderer<VH : RecyclerView.ViewHolder>
(val viewType: Int) {
abstract fun bindView(model: SimpleVO,
holder: VH)

abstract fun createViewHolder(parent: ViewGroup): VH
}

Obs.: Você pode recuperar este modelo no seu backend e delegar a responsabilidade de escolher qual a visualização e ordem que será mostrada ao utilizador.

Mas nem todos os problemas estão resolvidos… Ainda tem que criar os seus ViewHolders e ligá-los.

Existe uma solução para isto também!

Se você quiser que o seu Adapter seja realmente dinâmico, também precisa criar e prender os seus suportes, dinamicamente.

Para exemplificar, criei uma classe chamada ViewRenderer, e essa classe tem a responsabilidade de obter e criar o ViewHolder correto e de vincular a sua visualização. A classe abstrata tem este aspecto:

abstract class ViewRenderer<VH : RecyclerView.ViewHolder>
(val viewType: Int) {
abstract fun bindView(model: SimpleVO,
holder: VH)

abstract fun createViewHolder(parent: ViewGroup): VH
}

Depois, pode implementar esta classe abstrata e criar o seu próprio ViewRenderer e ViewHolder, desta forma:

class FirstComponentViewHolder(val textView : TextView) : RecyclerView.ViewHolder(textView)

class FirstComponentViewRenderer : ViewRenderer<FirstComponentViewHolder>(DynamicComponent.FIRSTCOMPONENT.ordinal){

override fun bindView(model: SimpleVO,
holder: FirstComponentViewHolder) {
holder.textView.text = model.value as? String
}

override fun createViewHolder(parent: ViewGroup)
: FirstComponentViewHolder =
FirstComponentViewHolder(TextView(parent.context))

}

No método createViewHolder(…) é preciso criar a sua instância ViewHolder como Declarado no construtor de classes. Para inflar a visualização, você pode apenas instanciá-la, ou utilizar o LayoutInflater para obter um layout xml utilizando o contexto principal que é recebido como parâmetro.
Em bindView(…) você recebe o modelo e o ViewHolder como parâmetro. Em seguida, pode conectar a sua visualização como quiser. Atribuindo os parâmetros do model a View.

Mas como é que o Adapter vai conhecer o seu Renderer? Para isso, a solução é uma lista de Renderes disponíveis, que você pode registar e chamar em createViewHolder(….) e onBindViewHolder(…). Como este:

override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): RecyclerView.ViewHolder =
renderers.get(viewType)?.let {
return it
.createViewHolder(parent)
} ?: EmptyViewHolder(FrameLayout(parent.context))

override fun onBindViewHolder(holder: RecyclerView.ViewHolder,
position: Int) {
vos[position]
.let { simpleVO ->
DynamicComponent
.getDynamicComponentByName(simpleVO.key)
?.ordinal
?.let {
renderers
.get(it)?.bindView(simpleVO,
holder)
}
}
}

Mas para obter um Renderer é necessário registar-se primeiro. Crie um método de registo, como este:

var renderers = SparseArray<ViewRenderer<RecyclerView.ViewHolder>>()

fun registerRenderer(renderer: ViewRenderer<*>) {
if (renderers.get(renderer.viewType) == null)
renderers.put(renderer.viewType,
renderer as ViewRenderer<RecyclerView.ViewHolder>)
}

Você pode registrar todos os Renderers que desejar e cada renderizador lidará com a criação e vinculação do ViewHolder.

Conclusão

Com esta estrutura, você agora pode usar um único Adapter para qualquer tela e componente que precise. Para isto, basta criar os seus ViewRenderes e Views como exemplificado acima. Também pode usar Frameworks como DataBinding para potenciar esta solução.

Aqui tem o repositório com a estrutura criada caso queiram conferir https://github.com/GustavoHSSantorio/Dynamic-Adapter

Referências de artigos que me baseei:

https://medium.com/android-news/simplifying-the-work-with-recyclerview-a64027bca8c3

Qualquer dúvida informe-me nos comentários, por favor.

Obrigado!!

--

--