Hallo allerseits!
In diesem Artikel möchte ich auf schnelle Optimierungsmöglichkeiten bei der Verwendung von RecyclerView eingehen.
RecyclerView ist eine Benutzeroberflächenkomponente, also ein Element, das der Benutzeroberfläche hinzugefügt werden kann, um eine Liste bequem anzuzeigen. Es ist in den Code integriert und enthält bereits Tools zum Anzeigen, Animieren und Optimieren der Liste und unterstützt auch Anpassungseinstellungen.
Damit RecyclerView ordnungsgemäß funktioniert, müssen Sie die folgenden Komponenten implementieren:
RecyclerView
, das zum Layout unserer Aktivität hinzugefügt werden muss;Adapter
, der Daten enthält, verarbeitet und mit der Liste verknüpft;ListAdapter
, Adapter aktualisiert und akzeptiert DiffUtil im Konstruktor;ViewHolder
, der der Ressourcenoptimierung dient und eine Art Container für alle in der Liste enthaltenen Elemente ist;DiffUtil
, das zur Optimierung der Liste verwendet wird.
Ich werde nicht näher darauf eingehen, wie man es von Grund auf implementiert. Das erfahren Sie unter dem Link .
Entfernen Sie beim Binden den Zugriff auf Ressourcen und das Casting im ViewHolder.
Nehmen Sie zum Beispiel den folgenden Code:
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 } }
Bei jedem Aufruf der bind
Methode werden die Aufrufe getString
und getColor
aufgerufen und toString
wird auch für die Zeichenfolgenkonvertierung aufgerufen. Auf diese Weise verbrauchen wir mehr Speicher. Und Sie müssen sicherstellen, dass die bind
Methode schnell ausgeführt wird, und Sie sollten darauf achten, dass die ViewHolder
Aufgabe nur Listenelemente anzeigt. Für Optimierungen ist es besser, die Zeichenfolgen eventId
, stateText
und color im TrackingUiModel
Objekt abzurufen.
Der optimierte Code sieht nun folgendermaßen aus:
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 } }
Wir haben alles über den ViewHolder
hinaus übernommen, alles was wir brauchen und fertig ist, erhalten wir im TrackingUiModel
.
Wenn Sie RecyclerView.Adapter
verwenden, sollten Sie zu ListAdapter
wechseln und DiffUtil
zusammen damit verwenden.
Nehmen Sie zum Beispiel den folgenden Code:
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() } }
Beim Hinzufügen von Listenelementen löscht der Aufruf der Methode setItems
die Liste, fügt Listenelemente hinzu und ruft abschließend notifyDataSetChanged
auf.
Stellen wir uns vor, wir ändern oder aktualisieren die Liste und die Methode setItems
wird jedes Mal aufgerufen. Dies wird ziemlich ressourcenintensiv sein. Aus diesem Grund ist es besser, ListAdapter
und DiffUtil
zu verwenden.
Unten sehen Sie einen geänderten TrackingAdapter
, der ListAdapter
implementiert und DiffUtil
verwendet. In meinem Beispiel ist es die Klasse TrackingDiffCallback
.
Um eine Liste hinzuzufügen, verwenden wir die Methode trackingAdapter.submitList
. Im Hintergrund übernimmt der Adapter die gesamte Arbeit zum Aktualisieren der Liste für uns.
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 } }
Es gibt Fälle wie das Hinzufügen von Listenelementen zu Favoriten, das Ändern eines Bildes oder das Ändern einer anderen Ansicht eines Listenelements. Wenn in all diesen Fällen Änderungen auftreten, wird die Bind-Methode im ViewHolder
erneut aufgerufen und wir bemerken visuell die Hervorhebung des Elements. Um dies zu verhindern, kommt uns die Nutzlast zu Hilfe.
Um die Nutzlast zu verwenden, nehmen wir eine Änderung an ViewHolder
, DiffUtil
und Adapter
vor.
In meinem Fall werde ich folgende Änderungen vornehmen.
In TrackingViewHolder fügen wir eine Bind-Methode mit den Daten hinzu, die geändert werden müssen.
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 }
In TrackingDiffCallback
überschreiben wir die Methode getChangePayload
und vergleichen das geänderte Feld. In meinem Fall ist es formattedTime
.
override fun getChangePayload(oldItem: TrackingUiModel, newItem: TrackingUiModel): Any? { if (oldItem.formattedTime != newItem.formattedTime) return newItem.formattedTime return super.getChangePayload(oldItem, newItem) }
In TrackingListAdapter,
überschreiben wir die Methode onBindViewHolder
mit Payloads. Wir prüfen, ob die Payload leer ist oder nicht. Wenn sie nicht leer ist, holen wir das erste Element und rufen die Bind-Methode für die Payload auf.
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) } }
Referenzlinks: