Я реализовал простой адаптер, но он заставляет RecyclerView не перерабатывать представления и вызывает onCreateViewHolder () для каждого элемента списка при прокрутке. Это вызывает рывки при прокрутке списка. Несколько пунктов, перечисленных ниже, не связаны с чрезмерным количеством вызовов onCreateViewHolder () , но я пробовал их, чтобы улучшить производительность прокрутки и избежать рывков. Вещи, которые я пробовал до сих пор:
- recyclerView.setHasFixedSize (true)
- recyclerView.recycledViewPool.setMaxRecycledViews (1, 10) с recyclerView.setItemViewCacheSize (10)
- recyclerView.setDrawingCacheEnabled (true) с recyclerView.setDrawingCacheQuality (View.DRAWING_CACHE_QUALITY_HIGH)
- установка высоты RecyclerView на "match_parent"
- Ранее использовался Kotlin Syntheti *, теперь перемещен * 10 to Android ViewBinding
- Переписать сложные вложенные макеты в Constraint Layout
- переопределить onFailedToRecycleView () , чтобы узнать, вызывается ли он, но он никогда не вызывался
Вот мой адаптер:
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.suppstore.R
import com.example.suppstore.common.Models.Brand
import com.example.suppstore.databinding.LiBrandBinding
import com.google.firebase.perf.metrics.AddTrace
class BrandsAdapter(list: ArrayList<Brand>, var listener: BrandClickListener?) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val VIEW_TYPE_LOADING = 0
private val VIEW_TYPE_NORMAL = 1
private var brandsList: ArrayList<Brand> = list
@AddTrace(name = "Brands - onCreateViewHolder", enabled = true)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == VIEW_TYPE_NORMAL) {
ViewHolder(
LiBrandBinding.inflate(
LayoutInflater.from(parent.context),
parent, false
)
)
} else {
LoaderHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.li_loader, parent, false)
)
}
}
@AddTrace(name = "Brands - onBindViewHolder", enabled = true)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ViewHolder)
holder.setData(brandsList[position], listener!!)
}
class ViewHolder(itemView: LiBrandBinding) : RecyclerView.ViewHolder(itemView.root) {
private val binding: LiBrandBinding = itemView
@AddTrace(name = "Brands - ViewHolder-setData", enabled = true)
fun setData(brand: Brand, listener: BrandClickListener) {
binding.cardItem.setOnClickListener { listener.onItemClick(brand) }
binding.tvBrandName.text = brand.name
binding.tvCount.text = brand.count.toString() + " Products"
}
}
class LoaderHolder(itemView: View) : RecyclerView.ViewHolder(itemView.rootView) {
}
@AddTrace(name = "Brands - addLoader", enabled = true)
fun addLoader() {
brandsList.add(Brand())
notifyItemInserted(brandsList.size - 1)
}
@AddTrace(name = "Brands - setData", enabled = true)
fun setData(newList: ArrayList<Brand>) {
this.brandsList = newList
notifyDataSetChanged()
}
@AddTrace(name = "Brands - removeLoader", enabled = true)
fun removeLoader() {
if (brandsList.size == 0)
return
val pos = brandsList.size - 1
brandsList.removeAt(pos)
notifyItemRemoved(pos)
}
override fun getItemViewType(position: Int): Int {
return if (brandsList.get(position).count == -1) {
VIEW_TYPE_LOADING
} else
VIEW_TYPE_NORMAL
}
interface BrandClickListener {
fun onItemClick(brand: Brand)
}
override fun getItemCount(): Int {
return brandsList.size
}
}
Вот элемент списка (li_brand):
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardItem"
android:layout_width="match_parent"
android:layout_height="85dp"
android:background="@color/app_black">
<TextView
android:id="@+id/tvBrandName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:textColor="@color/app_yellow"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/tvCount"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/tvCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="2dp"
android:textColor="@color/app_grey"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvBrandName" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="15dp"
android:src="@drawable/ic_baseline_arrow_forward_ios_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="@color/app_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Вот связанные функции во фрагменте
class BrandsFragment : Fragment() {
private val adapter = BrandsAdapter(ArrayList(), brandClickListener())
fun brandClickListener(): BrandsAdapter.BrandClickListener {
return object : BrandsAdapter.BrandClickListener {
override fun onItemClick(brand: Brand) {
activityViewModel?.setSelectedBrand(brand)
}
}
}
fun setupRecyclerView() {
val llManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
binding.recyclerView.layoutManager = llManager
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy > 0) { //check for scroll down
val visibleItemCount = llManager.childCount
val totalItemCount = llManager.itemCount
val firstVisibleItemPos = llManager.findFirstVisibleItemPosition()
if (loadWhenScrolled
&& visibleItemCount + firstVisibleItemPos >= totalItemCount
&& firstVisibleItemPos >= 0
) {
//ensures that last item was visible, so fetch next page
loadWhenScrolled = false
viewModel.nextPage()
}
}
}
})
binding.recyclerView.adapter = adapter
}
}
А вот и фрагмент xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/app_black"
android:focusableInTouchMode="true"
android:orientation="vertical"
tools:context=".Brands.BrandsFragment">
<androidx.appcompat.widget.SearchView
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:background="@drawable/bottom_line_yellow"
android:theme="@style/SearchViewTheme"
app:closeIcon="@drawable/ic_baseline_close_24"
app:iconifiedByDefault="false"
app:queryBackground="@android:color/transparent"
app:queryHint="Atleast 3 characters to search"
app:searchIcon="@drawable/ic_baseline_search_24" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>