¡Hola a todos!
En este artículo, me gustaría analizar formas rápidas de optimizar el uso de RecyclerView.
RecyclerView es un componente de la interfaz de usuario, es decir, un elemento que se puede agregar a la interfaz para mostrar una lista cómodamente. Está integrado en el código y ya contiene herramientas para mostrar, animar y optimizar la lista, y también admite configuraciones de personalización.
Para que RecyclerView funcione correctamente, debe implementar los siguientes componentes:
RecyclerView
, que debe agregarse al diseño de nuestra Actividad;Adapter
, que contiene, procesa y asocia datos con la lista;ListAdapter
, Adaptador actualizado y acepta DiffUtil en el constructor;ViewHolder
, que sirve para optimizar recursos y es una especie de contenedor para todos los elementos incluidos en la lista;DiffUtil
, que se utiliza para optimizar la lista.
No entraré en cómo implementarlo desde cero, puedes encontrarlo en el enlace .
Elimine el acceso a los recursos y la transmisión en ViewHolder al vincular.
Por ejemplo, tome el siguiente código:
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 se llama al método bind
, se llamarán las llamadas getString
y getColor
, y también se llamará toString
para la conversión de cadenas. De esta forma consumimos más memoria. Y debe asegurarse de que el método bind
se ejecute rápidamente y debe esforzarse por garantizar que la tarea ViewHolder
solo muestre elementos de la lista. Para optimizaciones, es mejor obtener la cadena eventId
, stateText
y color en el objeto TrackingUiModel
.
El código optimizado ahora se ve así:
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 } }
Hemos llevado todo más allá de ViewHolder
, todo lo que necesitamos y listo se recibirá en TrackingUiModel
.
Si está utilizando RecyclerView.Adapter
, entonces debe cambiar a ListAdapter
y utilizar DiffUtil
junto con él.
Por ejemplo, tome el siguiente código:
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() } }
Al agregar elementos de la lista, llamar al método setItems
borra la lista, agrega elementos de la lista y finalmente llama a notifyDataSetChanged
.
Imaginemos que cambiamos o actualizamos la lista y que se llamará al método setItems
cada vez; esto consumirá bastantes recursos. Por este motivo, es mejor utilizar ListAdapter
y DiffUtil
.
A continuación se muestra un TrackingAdapter
modificado que implementa ListAdapter
y usa DiffUtil
; en mi ejemplo, es la clase TrackingDiffCallback
.
Para agregar una lista, usamos el método trackingAdapter.submitList
y, bajo el capó, el adaptador hará todo el trabajo de actualizar la lista por nosotros.
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 } }
Existen casos como agregar elementos de la lista a favoritos, cambiar una imagen o cambiar alguna otra vista de un elemento de la lista. Cuando se producen cambios en todos estos casos, el método de vinculación se llama nuevamente en ViewHolder
y notamos visualmente el resaltado del elemento. Para evitar que esto suceda, la carga útil acude a nosotros en busca de ayuda.
Para usar la carga útil, hagamos un cambio en ViewHolder
, DiffUtil
y Adapter
.
En mi caso, haré los siguientes cambios.
En TrackingViewHolder, agregamos un método de enlace con los datos que deben cambiarse.
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 }
En TrackingDiffCallback
, anulamos el método getChangePayload
y comparamos el campo que se está cambiando. En mi caso, está formattedTime
.
override fun getChangePayload(oldItem: TrackingUiModel, newItem: TrackingUiModel): Any? { if (oldItem.formattedTime != newItem.formattedTime) return newItem.formattedTime return super.getChangePayload(oldItem, newItem) }
En TrackingListAdapter,
anulamos el método onBindViewHolder
con cargas útiles. Verificamos si la carga útil está vacía o no; si no está vacía, obtenemos el primer elemento y llamamos al método de vinculación para la 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) } }
Enlaces de referencia: