Herkese selam!
Bu yazıda RecyclerView kullanırken optimizasyon yapmanın hızlı yollarına bakmak istiyorum.
RecyclerView bir kullanıcı arayüzü bileşenidir, yani bir listeyi uygun şekilde görüntülemek için arayüze eklenebilen bir öğedir. Kodun içine yerleştirilmiştir ve listeyi görüntülemek, canlandırmak ve optimize etmek için zaten araçlar içerir ve ayrıca özelleştirme ayarlarını da destekler.
RecyclerView'ın doğru çalışması için aşağıdaki bileşenleri uygulamanız gerekir:
RecyclerView
;Adapter
;ListAdapter
, Adapter güncellendi ve yapıcıda DiffUtil'i kabul etti;ViewHolder
;DiffUtil
.
Sıfırdan nasıl uygulanacağına girmeyeceğim, linkte bulabilirsiniz.
Bağlama sırasında ViewHolder'da kaynaklara ve yayına erişimi kaldırın.
Örneğin aşağıdaki kodu alın:
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
yöntemi her çağrıldığında getString
ve getColor
çağrıları çağrılacak ve ayrıca dize dönüşümü için toString
çağrılacaktır. Bu şekilde daha fazla hafıza tüketiriz. bind
yönteminin hızlı bir şekilde yürütüldüğünden emin olmanız ve ViewHolder
görevinin yalnızca liste öğelerini görüntülemesini sağlamaya çalışmalısınız. Optimizasyonlar için, TrackingUiModel
nesnesinde eventId
, stateText
ve color dizesini almak daha iyidir.
Optimize edilmiş kod artık şöyle görünüyor:
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
ötesine geçen her şeyi aldık, ihtiyacımız olan her şey hazır olarak TrackingUiModel
alınacak.
RecyclerView.Adapter
kullanıyorsanız ListAdapter
geçmeli ve onunla birlikte DiffUtil
kullanmalısınız.
Örneğin aşağıdaki kodu alın:
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() } }
Liste öğeleri eklerken setItems
yönteminin çağrılması listeyi temizler, liste öğelerini ekler ve son olarak notifyDataSetChanged
çağırır.
Listeyi değiştirdiğimizi veya güncellediğimizi ve her seferinde setItems
yönteminin çağrılacağını düşünelim; bu oldukça kaynak tüketen bir işlem olacaktır. Bu nedenle ListAdapter
ve DiffUtil
kullanmak daha iyidir.
Aşağıda ListAdapter
uygulayan ve DiffUtil
kullanan değiştirilmiş bir TrackingAdapter
bulunmaktadır; benim örneğimde TrackingDiffCallback
sınıfıdır.
Bir liste eklemek için trackingAdapter.submitList
yöntemini kullanırız ve aslında bağdaştırıcı listeyi güncellemeyle ilgili tüm işi bizim için yapar.
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 } }
Liste öğelerinin favorilere eklenmesi, bir görselin değiştirilmesi veya bir liste öğesinin başka bir görünümünün değiştirilmesi gibi durumlar vardır. Tüm bu durumlarda değişiklik meydana geldiğinde, ViewHolder
bağlama yöntemi tekrar çağrılır ve öğenin vurgulanmasını görsel olarak fark ederiz. Bunun olmasını önlemek için payload yardım için bize geliyor.
Payload'ı kullanmak için ViewHolder
, DiffUtil
ve Adapter
bir değişiklik yapalım.
Benim durumumda aşağıdaki değişiklikleri yapacağım.
TrackingViewHolder'da değiştirilmesi gereken verileri içeren bir bağlama yöntemi ekliyoruz.
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
yöntemini geçersiz kılıyoruz ve değiştirilen alanı karşılaştırıyoruz. Benim durumumda bu formattedTime
.
override fun getChangePayload(oldItem: TrackingUiModel, newItem: TrackingUiModel): Any? { if (oldItem.formattedTime != newItem.formattedTime) return newItem.formattedTime return super.getChangePayload(oldItem, newItem) }
TrackingListAdapter,
onBindViewHolder
yöntemini payloadlarla geçersiz kılıyoruz. Payload'un boş olup olmadığını kontrol ediyoruz, boş değilse daha sonra ilk elemanı alıyoruz ve payload için bağlama yöntemini çağırıyoruz.
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) } }
Referans bağlantıları: