I'm developing an Android application with RecyclerView. The Layout manager type is GridLayoutManager, the list items are arranged in two columns.
val products = listOf (
Product("Product 1", "Description of the product 1", 124, 89, 25, 4.5, 1),
Product("Product 2", "Description of the product 2", 389, 256, 35, 4.2, 12),
Product("Product 3", "Description of the product 3", 456, 315, 40, 4.3, 36),
// Total 16 products...
)
val layoutManager = GridLayoutManager(this, 2)
binding.recyclerView.layoutManager = layoutManager
val adapter = ProductAdapter(this)
binding.recyclerView.adapter = adapter
adapter.setData(products)
This is what it looks like: https://i.stack.imgur.com/N9GMA.jpg
The list item itself is a CardView, inside of which there are 11 views (7 TextViews, 2 ImageViews and 2 ImageButtons), organized using ConstraintLayout. You can find the entire layout in my GitHub repository, file item_product.xml.
In the adapter, in the ProductViewHolder class, I set the desired text to the TextViews, the image to the ImageView, and also an OnClickListener for the ImageButton (add to favorites or remove from favorites):
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
val product = currentProducts[position]
holder.bind(product)
}
inner class ProductViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val binding = ItemProductBinding.bind(view)
private lateinit var product: Product
fun bind(product: Product) {
this.product = product
initFavoriteButton()
binding.imageView.setImageResource(R.drawable.cream)
binding.titleTextView.text = product.title
binding.subtitleTextView.text = product.subtitle
binding.oldPriceTextView.text = product.oldPrice.toString()
binding.priceTextView.text = product.price.toString()
binding.discountTextView.text = context.resources.getString(R.string.discount, product.discount)
binding.ratingTextView.text = product.rating.toString()
binding.countTextView.text = context.resources.getString(R.string.feedback_count, product.countReviews)
}
private fun initFavoriteButton() {
setFavoriteButtonIcon()
binding.addToFavoriteButton.setOnClickListener {
product.isFavorite = !product.isFavorite
setFavoriteButtonIcon()
}
}
private fun setFavoriteButtonIcon() {
if (product.isFavorite) {
binding.addToFavoriteButton.setImageResource(R.drawable.ic_heart_filled)
} else {
binding.addToFavoriteButton.setImageResource(R.drawable.ic_heart_stroke)
}
}
}
The problem is that when scrolling the list, and specifically at the moment when the next row of two elements is about to appear on the screen, the list noticeably slows down.
I tried to debug the adapter and discovered this: no more than 6 list items can be displayed on the screen at a time (3 rows of 2 cards each), and when opening the application, the adapter's onBindViewHolder method is called 6 times. This means that the adapter generates view holders only for those elements that are currently visible on the screen. When I'm scrolling through the list and the next two items are about to appear at the bottom of the screen, only then the onBindViewHolder method gets called two more times, at which point the scroll delay occurs.
My question is this: can I somehow force the adapter to call the onBindViewHolder() method not only for those six elements that are visible on the screen, but also for the next row of two elements? As far as I know, this is exactly how RecyclerView should behave by default: prepare for display not only list items currently visible on the screen, but also items that are off the screen. Maybe I need to add some settings for this? Or am I completely mistaken, and the reason for these lags is something else... I would be grateful for any help!
The source code of my project is available here: https://github.com/Vladimir127/GridLayoutManagerExample