Oi pessoal!
Neste artigo, gostaria de examinar maneiras rápidas de otimizar ao usar o RecyclerView.
RecyclerView é um componente da interface do usuário, ou seja, um elemento que pode ser adicionado à interface para exibir uma lista de forma conveniente. Ele está embutido no código e já contém ferramentas para exibir, animar e otimizar a lista, além de oferecer suporte a configurações de personalização.
Para que o RecyclerView funcione corretamente, você deve implementar os seguintes componentes:
RecyclerView
, que deve ser adicionado ao layout da nossa Activity;Adapter
, que contém, processa e associa dados à lista;ListAdapter
, Adapter atualizado e aceita DiffUtil no construtor;ViewHolder
, que serve para otimizar recursos e é uma espécie de container para todos os elementos incluídos na lista;DiffUtil
, que é usado para otimizar a lista.
Não vou entrar em como implementá-lo do zero, você pode encontrá-lo no link .
Remova o acesso aos recursos e a transmissão no ViewHolder durante a vinculação.
Por exemplo, pegue o código abaixo:
data class TrackingUiModel( val id: Long, val title: String, @ColorRes val color: Int, val eventId: Long, val state: TrackingState, val startTime: Long, val endTime: Long, val countTime: Long, val formattedTime: String, ) class TrackingViewHolder(itemView: View): ViewHolder(itemView) { private val binding : TrackingItemBinding by viewBinding() @SuppressLint("SetTextI18n") fun bind(data: TrackingUiModel) { val stateText = when (data.state) { TrackingState.START -> itemView.context.getString(R.string.in_progress) TrackingState.PAUSE -> itemView.context.getString(R.string.pause) else -> { "" } } binding.eventId.text = data.eventId.toString() binding.eventTitle.text = "${data.title} $stateText" binding.eventColor.setBackgroundColor(ContextCompat.getColor(itemView.context, data.color)) binding.countTextView.text = data.formattedTime } }
Cada vez que o método bind
for chamado, as chamadas getString
e getColor
serão chamadas, e toString
também será chamado para conversão de string. Dessa forma consumimos mais memória. E você precisa ter certeza de que o método bind
é executado rapidamente e deve se esforçar para garantir que a tarefa ViewHolder
exiba apenas itens de lista. Para otimizações, é melhor obter a string eventId
, stateText
e color no objeto TrackingUiModel
.
O código otimizado agora fica assim:
data class TrackingUiModel( val id: Long, val title: String, val color: Int, val eventId: String, val state: TrackingState, val stateText: String, val startTime: Long, val endTime: Long, val countTime: Long, val formattedTime: String, ) class TrackingViewHolder(itemView: View): ViewHolder(itemView) { private val binding : TrackingItemBinding by viewBinding() fun bind(data: TrackingUiModel) { binding.eventId.text = data.eventId binding.eventTitle.text = "${data.title} ${data.stateText}" binding.eventColor.setBackgroundColor(data.color) binding.countTextView.text = data.formattedTime } }
Levamos tudo além do ViewHolder
, tudo que precisamos e pronto será recebido no TrackingUiModel
.
Se você estiver usando RecyclerView.Adapter
então você deve mudar para ListAdapter
e usar DiffUtil
junto com ele.
Por exemplo, pegue o código abaixo:
class TrackingAdapter: RecyclerView.Adapter<TrackingViewHolder>(){ private val mItems = mutableListOf<TrackingUiModel>() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackingViewHolder { return TrackingViewHolder( LayoutInflater.from(parent.context) .inflate(R.layout.tracking_item, parent, false)) } override fun getItemCount(): Int = mItems.size override fun onBindViewHolder(holder: TrackingViewHolder, position: Int) { holder.bind(mItems[position]) } fun setItems(items: List<TrackingUiModel>){ mItems.clear() mItems.addAll(items) notifyDataSetChanged() } }
Ao adicionar elementos da lista, chamar o método setItems
limpa a lista, adiciona elementos da lista e, finalmente, chama notifyDataSetChanged
.
Vamos imaginar que alteramos ou atualizamos a lista, e o método setItems
será chamado a cada vez; isso consumirá bastante recursos. Por esse motivo, é melhor usar ListAdapter
e DiffUtil
.
Abaixo está um TrackingAdapter
modificado que implementa ListAdapter
e usa DiffUtil
; no meu exemplo, é a classe TrackingDiffCallback
.
Para adicionar uma lista, usamos o método trackingAdapter.submitList
e, nos bastidores, o adaptador fará todo o trabalho de atualização da lista para nós.
class TrackingAdapter: ListAdapter<TrackingUiModel, TrackingViewHolder>(TrackingDiffCallback()){ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackingViewHolder { return TrackingViewHolder( LayoutInflater.from(parent.context) .inflate(R.layout.tracking_item, parent, false)) } override fun onBindViewHolder(holder: TrackingViewHolder, position: Int) { holder.bind(getItem(position)) } }
class TrackingDiffCallback: DiffUtil.ItemCallback<TrackingUiModel>() { override fun areItemsTheSame(oldItem: TrackingUiModel, newItem: TrackingUiModel): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: TrackingUiModel, newItem: TrackingUiModel): Boolean { return oldItem == newItem } }
Existem casos como adicionar itens de lista aos favoritos, alterar uma imagem ou alterar alguma outra visualização de um item de lista. Quando ocorrem alterações em todos esses casos, o método bind é chamado novamente no ViewHolder
e notamos visualmente o destaque do elemento. Para evitar que isso aconteça, a carga vem até nós em busca de ajuda.
Para usar o payload, vamos fazer uma alteração em ViewHolder
, DiffUtil
e Adapter
.
No meu caso, farei as seguintes alterações.
Em TrackingViewHolder, adicionamos um método de ligação com os dados que precisam ser alterados.
fun bind(data: TrackingUiModel, newTime: String) { binding.eventId.text = data.eventId binding.eventTitle.text = "${data.title} ${data.stateText}" binding.eventColor.setBackgroundColor(data.color) binding.countTextView.text = newTime }
Em TrackingDiffCallback
, substituímos o método getChangePayload
e comparamos o campo que está sendo alterado. No meu caso, é formattedTime
.
override fun getChangePayload(oldItem: TrackingUiModel, newItem: TrackingUiModel): Any? { if (oldItem.formattedTime != newItem.formattedTime) return newItem.formattedTime return super.getChangePayload(oldItem, newItem) }
Em TrackingListAdapter,
substituímos o método onBindViewHolder
por cargas úteis. Verificamos se a carga útil está vazia ou não, se não estiver vazia, então obtemos o primeiro elemento e chamamos o método bind para a carga útil.
override fun onBindViewHolder(holder: TrackingViewHolder, position: Int, payloads: MutableList<Any>) { if (payloads.isEmpty()) { super.onBindViewHolder(holder, position, payloads) } else { val newTime = payloads.firstOrNull() as? String ?: "" holder.bind(getItem(position), newTime) } }
Links de referência: