paint-brush
3 быстрых способа оптимизировать RecyclerViewby@azamatnurkhojayev
1,015
1,015

3 быстрых способа оптимизировать RecyclerView

В этой статье я хотел бы рассмотреть быстрые способы оптимизации при использовании RecyclerView. RecyclerView — это компонент пользовательского интерфейса, то есть элемент, который можно добавить в интерфейс для удобного отображения списка.
featured image - 3 быстрых способа оптимизировать RecyclerView
Azamat Nurkhojayev HackerNoon profile picture


Всем привет!


В этой статье я хотел бы рассмотреть быстрые способы оптимизации при использовании RecyclerView.


RecyclerView — это компонент пользовательского интерфейса, то есть элемент, который можно добавить в интерфейс для удобного отображения списка. Он встроен в код и уже содержит инструменты для отображения, анимации и оптимизации списка, а также поддерживает настройки кастомизации.

Основные компоненты RecyclerView

Для корректной работы RecyclerView необходимо реализовать следующие компоненты:

  • RecyclerView , который необходимо добавить в макет нашей Activity;
  • Adapter , который содержит, обрабатывает и связывает данные со списком;
  • ListAdapter обновил адаптер и принимает DiffUtil в конструкторе;
  • ViewHolder , служащий для оптимизации ресурсов и являющийся своеобразным контейнером для всех элементов, включенных в список;
  • DiffUtil , который используется для оптимизации списка.


Я не буду вдаваться в подробности, как это реализовать с нуля, вы можете найти это по ссылке .

Первая оптимизация ViewHolder.

Удалить доступ к ресурсам и кастинг во ViewHolder при привязке.


Например, возьмите код ниже:

 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 } }


Каждый раз при вызове метода bind будут вызываться вызовы getString и getColor , а также будет вызываться toString для приведения строк. Таким образом мы потребляем больше памяти. И вам нужно убедиться, что метод bind выполняется быстро, и вы должны стремиться к тому, чтобы задача ViewHolder отображала только элементы списка. Для оптимизации лучше получить строку eventId , stateText и цвет в объекте TrackingUiModel .


Оптимизированный код теперь выглядит так:

 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 } }


Мы взяли все, что выходит за рамки ViewHolder , все необходимое и готовое мы получим в TrackingUiModel .

Во-вторых, мы будем использовать ListAdapter и DiffUtil.

Если вы используете RecyclerView.Adapter , вам следует переключиться на ListAdapter и использовать вместе с ним DiffUtil .


Например, возьмите код ниже:

 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() } }


При добавлении элементов списка вызов метода setItems очищает список, добавляет элементы списка и, наконец, вызывает notifyDataSetChanged .


Представим, что мы изменяем или обновляем список, и каждый раз будет вызываться метод setItems ; это будет довольно ресурсозатратно. По этой причине лучше использовать ListAdapter и DiffUtil .


Ниже приведен модифицированный TrackingAdapter , который реализует ListAdapter и использует DiffUtil ; в моем примере это класс TrackingDiffCallback .


Чтобы добавить список, мы используем метод trackingAdapter.submitList , и внутри адаптер всю работу по обновлению списка выполнит за нас.


 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 } }


В-третьих, используйте полезную нагрузку.

Бывают такие случаи, как добавление элементов списка в избранное, изменение изображения или изменение какого-либо другого вида элемента списка. Когда во всех этих случаях происходят изменения, во ViewHolder снова вызывается метод привязки, и мы визуально замечаем подсветку элемента. Чтобы этого не произошло, к нам на помощь приходит полезная нагрузка.


Чтобы использовать полезную нагрузку, давайте внесем изменения в ViewHolder , DiffUtil и Adapter .


В моем случае я внесу следующие изменения.


В TrackingViewHolder добавляем метод привязки с данными, которые необходимо изменить.

 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 }


В TrackingDiffCallback мы переопределяем метод getChangePayload и сравниваем изменяемое поле. В моем случае это formattedTime .

 override fun getChangePayload(oldItem: TrackingUiModel, newItem: TrackingUiModel): Any? { if (oldItem.formattedTime != newItem.formattedTime) return newItem.formattedTime return super.getChangePayload(oldItem, newItem) }


В TrackingListAdapter, мы переопределяем метод onBindViewHolder с помощью полезных данных. Проверяем, пуста ли полезная нагрузка или нет, если она не пуста, то получаем первый элемент и вызываем метод привязки полезной нагрузки.

 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) } } 



Без полезной нагрузки


С полезной нагрузкой



Справочные ссылки: