Search code examples
androidandroid-recyclerviewandroid-viewholderrecyclerview-layoutgridlayoutmanager

RecyclerView.Adapter and GridLayoutManager with spanCount great than 4 continuously recreates ViewHolders


I am working with GridLayoutManager and I have encountered unexpected RecyclerView's behaviour. If spanCount is greater than 4, the RecyclerView continuously recreates ViewHolders on scrolling.

    override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.main_activity)

       val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
       recyclerView.layoutManager = GridLayoutManager(this, 7)
       recyclerView.adapter = Adapter()
   }

   private class Adapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
       //continuously invokes while scrolling:
       override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
           val view:View = TextView(parent.context).apply {
               text = "Hello!"
           }
           return object : RecyclerView.ViewHolder(view) {}
       }

       override fun getItemCount(): Int = 3500

       override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {}
   } 

How to fix it and force RecyclerView.Adapter to reuse ViewHolders?


Solution

  • I reproduced your issue with this code (copy-pasteable, no resource files required):

    class RecActivity : AppCompatActivity() {
        lateinit var recyclerView: RecyclerView
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            recyclerView = RecyclerView(this)
            setContentView(recyclerView)
            recyclerView.layoutManager = GridLayoutManager(this, 7)
            recyclerView.adapter = Adapter()
        }
    
        inner class Adapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
            var vhCount = 0
            override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
                val txtView = AppCompatTextView(this@RecActivity)
                txtView.tag = vhCount++.toString()
                txtView.gravity = Gravity.CENTER
                title = vhCount.toString()  // display # of created VHs in title
                return object : RecyclerView.ViewHolder(txtView){}
            }
    
            override fun getItemCount() = 3500
    
            override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
                (holder.itemView as TextView).text = "$position (${holder.itemView.tag})"
            }
        }
    }
    

    The problem is there's not enough views to fill out entire rows in the RecycledViewPool. By default there's only 5 items per ViewType, so having wide rows of 7 force creation of more ViewHolders while scrolling. To fix this issue, simply increase size of your RecycledViewPool like so (in onCreate):

    recyclerView.layoutManager = GridLayoutManager(this, 7)
    recyclerView.adapter = Adapter()
    // add line below: 0 is default itemViewType, 14 is two rows of items which should be enough
    recyclerView.recycledViewPool.setMaxRecycledViews(0, 14)