Hoje em dia, o Jetpack Compose ganha uma adoção e definitivamente merece. É uma verdadeira mudança de paradigma no desenvolvimento da interface do usuário . Tanto padrão de criação de listas foi eliminado lá. E é lindo. do Android Acredito que muitos projetos ainda estão usando o framework View e, portanto, usando o RecyclerView por ter listas. Existem algumas razões para isso, como eu vejo: Algumas soluções proprietárias internas em torno do RecyclerView não combinam facilmente com o Compose, portanto, os custos de migração são altos Os desenvolvedores ainda não estão familiarizados o suficiente com o Compose Não há confiança sobre o desempenho do Compose (ainda) De qualquer forma, o Compose é o futuro e é por isso que estou usando a palavra “vintage” no título. Vintage significa velho, mas ouro. Este artigo é o resultado de uma compilação da minha experiência com o RecyclerView e, para aqueles que ainda estão no caminho certo, contarei sobre minha visão de construir um framework em torno do RecyclerView. Requisitos A estrutura deve fornecer um nível razoável de escalabilidade. Portanto, os principais cenários devem ser tratados com um mínimo de clichê, mas não deve atrapalhar caso o usuário queira criar algo personalizado. Eu definiria o conjunto básico de cenários durante o trabalho com o RecyclerView da seguinte forma: Atualização efetiva de dados. A melhor prática conhecida durante o trabalho com RecyclerView é usar a API para atualizar dados na lista. Não vamos reinventar uma roda aqui, então vamos nos concentrar em alavancar e também tentar reduzir uma cerimônia para sua implementação. DiffUtil DiffUtil Definição de listas heterogêneas. É natural ter vários tipos de visualizações em uma lista. As telas de dispositivos móveis são limitadas pela altura e diferentes dispositivos têm diferentes tamanhos de tela. Portanto, mesmo que seu conteúdo se ajuste à tela em um dispositivo sem rolagem, pode ser necessário rolar em outro dispositivo. É por isso que pode ser uma boa ideia desenvolver algumas telas no topo do RecyclerView. Itens da lista de decoração Decorações são um caso muito comum para listas. E embora você possa ter decorações mescladas a layouts de itens, o que pode parecer simples, não é muito flexível: os mesmos tipos de itens podem exigir decorações diferentes em telas diferentes ou até mesmo decorações diferentes dependendo da posição do item em uma lista. opções de fundação A primeira opção é fazer tudo do zero. Está dando controle total sobre a implementação, mas sempre podemos fazer isso. Existem várias estruturas de código aberto de terceiros que permitem trabalhar com o RecyclerView com menos clichê. Não vou enumerar todos, mas quero mostrar dois deles com abordagens completamente opostas: Epóxi Solução do AirBnb para criar telas complexas com RecyclerView. Ele está adicionando um monte de novas APIs e até mesmo uma geração de código para reduzir o clichê o máximo possível. Mas não consigo me livrar da sensação de que o código resultante parece estranho. Além disso, um processamento de anotação adiciona outra camada de complexidade. Delegados do Adaptador Biblioteca minúscula, que na verdade fornece um conjunto conciso de funções e interfaces que apenas sugerem uma maneira de dividir sua lista heterogênea em um conjunto de adaptadores delegados. Na verdade, você pode criar essa própria solução em um tempo relativamente curto. Eu gosto de pequenas soluções nativas que sejam simples o suficiente para manter todo o comportamento na ponta dos dedos e que não escondam debaixo do tapete todos os detalhes de implementação. É por isso que acredito que AdapterDelagates e seus princípios são bons candidatos para a base de nosso framework. Lista de itens A coisa mais básica para o começo é uma declaração comum de itens de lista. Os itens da lista no mundo real são muito diferentes, mas a única coisa em que temos confiança é que devemos poder compará-los. Sugiro consultar a API de DiffUtil.ItemCallback interface ListItem { fun isItemTheSame(other: ListItem): Boolean fun isContentTheSame(other: ListItem): Boolean { return this == other } } É uma declaração minimalista (e, portanto, sólida, IMO) de um item de lista na estrutura. A semântica dos métodos deve ser a mesma de um DiffUtil usando: verifica a identidade de e isItemTheSame other this verifica os dados em e isContentTheSame other this A maneira mais comum de fornecer uma identidade estável e exclusiva é usar identificadores provenientes do servidor. Então, vamos ter uma implementação abstrata para reduzir um pouco do clichê possível: abstract class DefaultListItem : ListItem { abstract val id: String override fun isItemTheSame(other: ListItem): Boolean = when { other !is DefaultListItem -> false this::class != other::class -> false else -> this.id == other.id } } na grande maioria dos casos será bastante simples (uma verificação de igualdade), se as implementações de ListItem forem uma . isContentTheSame data class Manter separado ainda é razoável nos casos em que você tem itens em sua lista que não são projeções de dados do servidor e não possuem nenhuma identidade sã. Por exemplo, se você tiver um rodapé e um cabeçalho como itens em uma lista ou tiver um único item de algum tipo: ListItem data class HeaderItem(val title: String) : ListItem { override fun isItemTheSame(other: ListItem): Boolean = other is HeaderItem } A abordagem sugerida nos permite ter uma implementação de retorno de chamada muito natural e única: DiffUtil object DefaultDiffUtil : DiffUtil.ItemCallback<ListItem>() { override fun areItemsTheSame(oldItem: ListItem, newItem: ListItem): Boolean = oldItem.isItemTheSame(newItem) override fun areContentsTheSame(oldItem: ListItem, newItem: ListItem): Boolean = oldItem.isContentTheSame(newItem) } E a etapa final aqui é uma declaração do adaptador padrão que estende de AdapterDelegates: AsyncListDifferDelegationAdapter class CompositeListAdapter : AsyncListDifferDelegationAdapter<ListItem>(DefaultDiffUtil) listas heterogêneas A biblioteca AdapterDelegates fornece uma maneira prática de declarar delegados para um determinado tipo de exibição. A única coisa que podemos melhorar é reduzir um pouco o clichê: inline fun <reified I : ListItem, V : ViewBinding> defaultAdapterDelegate( noinline viewBinding: (layoutInflater: LayoutInflater, parent: ViewGroup) -> V, noinline block: AdapterDelegateViewBindingViewHolder<I, V>.() -> Unit ) = adapterDelegateViewBinding<I, ListItem, V>(viewBinding = viewBinding, block = block) Para fins de demonstração, vamos considerar um exemplo de como seria a declaração de algum , contendo um campo de texto: TitleListItem data class TitleListItem( override val id: String, val title: String, ) : DefaultListItem() e delegar para isso fun titleItemDelegate(onClick: ((String) -> Unit)) = defaultAdapterDelegate< TitleListItem, TitleListItemBinding >(viewBinding = { inflater, root -> TitleListItemBinding.inflate(inflater,root,false) }) { itemView.setOnClickListener { it(item.id) } bind { binding.root.text = item.title } } } listas decoradas Originalmente, por causa da API do , as decorações de configuração são feitas separadamente dos dados de configuração para listar. Se você tem alguma lógica para decorações como: , ou do que criar a decoração se torna uma dor. RecyclerView all items should have offset at the bottom except the last one all items should have a divider at the bottom, but headers should have an offset at bottom Freqüentemente, as equipes vêm com o decorador “deus” que pode ser configurado: class SmartDividerItemDecorator( val context: Context, val skipDividerFor: Set<Int> = emptySet(), val showDividerAfterLastItem: Boolean = false, val showDividerBeforeFirstItem: Boolean = false, val dividerClipToPadding: Boolean = true, val dividerPaddingLeft: Int = 0, val dividerPaddingRight: Int = 0 ) : ItemDecoration() Você só pode imaginar os detalhes da implementação e como ela é frágil e não escalável. Podemos usar uma abordagem antiga comprovada com imitação de alguma API e delegar uma implementação. RecyclerView Então, estou começando com um dado: interface Decoration Marcador de interface para decoração, que será apresentado futuramente. interface HasDecorations { var decorations: List<Decoration> } Essa interface deve ser implementada por nossos itens de dados para declarar que esse item em particular deseja ser decorado com uma determinada lista de decorações. As decorações são em prol da simplicidade para alterá-las em tempo de execução, se necessário. var Muitas vezes, um item tem uma única decoração, portanto, para reduzir o clichê envolvendo um único item em , podemos fazer essa manobra: listOf() interface HasDecoration : HasDecorations { var decoration: Decoration override var decorations: List<Decoration> get() = listOf(decoration) set(value) { decoration = when { value.isEmpty() -> None value.size == 1 -> value.first() else -> throw IllegalArgumentException("Applying few decorations to HasDecoration instance is prohibited. Use HasDecorations") } } } O próximo estágio é imitar a API : ItemDecoration interface DecorationDrawer<T : Decoration> { fun getItemOffsets( outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State, decoration: T ) fun onDraw( c: Canvas, view: View, parent: RecyclerView, state: RecyclerView.State, decoration: T ) } Como você pode ver, ele repete completamente com uma única diferença – agora ele está ciente do tipo de decoração e da instância que está chegando. ItemDecoration Possíveis implementações para é um tópico do segundo artigo, agora vamos focar apenas na interface e como ela deve ser tratada para apresentar as decorações. DecorationDrawer class CompositeItemDecoration( private val context: Context ) : RecyclerView.ItemDecoration() { private val drawers = mutableMapOf<Class<Decoration>, DecorationDrawer<Decoration>>() // [1] private fun applyDecorationToView(parent: RecyclerView, view: View, applyDecoration: (Decoration) -> Unit) { val position = getAdapterPositionForView(parent, view) // [2] when { // [3] position == RecyclerView.NO_POSITION -> return adapter.items[position] is HasDecorations -> { val decoration = (adapter.items[position] as HasDecorations).decorations decoration.forEach(applyDecoration) } else -> return } } override fun getItemOffsets( outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State ) { applyDecorationToView(parent, view) { decoration -> val drawer = getDrawerFor(decoration) drawer.getItemOffsets(outRect, view, parent, state, decoration) } } override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { parent.forEach { child -> applyDecorationToView(parent, child) { decoration -> val drawer = getDrawerFor(decoration) drawer.onDraw(c, child, parent, state, decoration) } } } private fun getDrawerFor(decoration: Decoration): DecorationDrawer<Decoration> { // [4] } private fun getAdapterPositionForView(parent: RecyclerView, view: View): Int { var position = parent.getChildAdapterPosition(view) if (position == RecyclerView.NO_POSITION) { val oldPosition = parent.getChildViewHolder(view).oldPosition if (oldPosition in 0 until (parent.adapter?.itemCount ?: 0)) { position = oldPosition } } return position } } Cache para gavetas criadas não é algo específico para aplicação de decoração, é apenas um método para resolver corretamente a posição do adaptador para determinada exibição getAdapterPositionForView() Iterando pelas decorações da instância HasDecorations Esta peça é tema do segundo artigo “Decorações à la carte” Conclusão Este artigo descobriu uma abordagem para criar uma estrutura de lista em torno de . Delegação usada como princípio básico para agrupar itens de lista e decorações de lista. RecyclerView Acredito que as principais vantagens da solução sugerida são: Natividade. A implementação não depende de bibliotecas de terceiros, o que pode reduzir a flexibilidade no futuro. O AdapterDelegates foi escolhido pensando exatamente nesses critérios – é muito simples e natural e todo o comportamento ao seu alcance. Escalabilidade. Graças ao princípio de delegação e separação de preocupações, somos capazes de desacoplar implementações de itens delegados ou gavetas de decoração. É fácil integrar-se de forma incremental a um projeto existente Na segunda parte, discutiremos todos os tipos de decorações de lista que provavelmente esperamos ver em um aplicativo Android.