How does the recyclerview adapter know to call onCreateViewHolder?

I just saw this example class for an Adapter for Recyclerview and I'm a bit confused on how it knows to call onCreateViewHolder, onBindViewHolder, etc just from adding an Item object to a list? Does it have something to do with the line notifyItemInserted(items.size - 1) ? Is it that whenever this method is called the onCreateViewHolder method is recalled with for that item or? Adapter:

class ListAdapter (
    private val items: MutableList<Item>
) : RecyclerView.Adapter <ListAdapter.ListViewHolder>() {

    class ListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
        return ListViewHolder(
            LayoutInflater.from(parent.context).inflate(R.layout.list_items, parent, false)
        )
    }

    override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
        val currItem = items[position]

        holder.itemView.apply {
            tv_item.text = currItem.title
            cb_item.isChecked = currItem.checked

            crossItem(tv_item, currItem.checked)

            cb_item.setOnCheckedChangeListener { _, isChecked ->
                crossItem(tv_item, isChecked)

                currItem.checked = !currItem.checked

                items.removeAll { item ->
                    item.checked
                }
                notifyDataSetChanged()
            }
        }
    }

    override fun getItemCount(): Int {
        return items.size
    }

    private fun crossItem (itemText: TextView, checked: Boolean) {

        if (checked){
            //dk wtf paint flags is
            itemText.paintFlags = itemText.paintFlags or STRIKE_THRU_TEXT_FLAG
        }
        //else remove
        else {
            itemText.paintFlags = itemText.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
        }
    }

    fun addItem (item: Item){

        items.add (item)

        notifyItemInserted(items.size - 1)
    }

}

Item Class:

data class Item (
    val title: String,
    var checked: Boolean = false
)
{
}

2 answers

  • answered 2021-11-23 02:00 avalerio

    override fun getItemCount(): Int {
        return items.size
    }
    

    This function is the key, it knows how many to create and how many to bind by knowing how many there are in total. The amount of ViewHolders created is more based on how many Views can fit on the screen at one time.

    This gets more complex when you have different view types, as it will sometimes has to create more ViewHolders than what was required from the start as view types change.

    The notify... functions just let the Adapter know it needs to "re-look" at the List.

  • answered 2021-11-23 02:36 Tenfour04

    Whenever the Adapter needs to provide a new view for the RecyclerView to draw, it checks if it has an unused ViewHolder in its pool. If it doesn't, it calls onCreateViewHolder() so it can create one. Then it calls onBindViewHolder() for the ViewHolder that came from either source so the contained view can be prepared before being added to the layout.

    If you call one of the notify methods, that triggers it to refresh whichever item rows are affected. It will return any removed rows to the ViewHolder pool and then follow the above steps to get the views it needs for new rows. If you use a notify...changed method, it will only need to use onBindViewHolder() for the applicable rows. When you use the nuclear option notifyDataSetChanged(), it returns all items to the pool.

    When the RecyclerView is first displayed, or when the layout is resized, those actions will possibly trigger the need to show more rows. When you scroll the list, items that scroll off the screen are returned to the ViewHolder pool, and when new items scroll into view, ViewHolders need to be created or acquired from the pool as explained above.

    By the way, this is going to look ugly because it refreshes the whole list even though only some items are removed:

    items.removeAll { item ->
        item.checked
    }
    notifyDataSetChanged()
    

    I recommend this instead so you get a nice transition:

    for (i in items.indices.reversed()) {
        if (items[i].checked) {
            items.removeAt(i)
            notifyItemRemoved(i)
        }
    }
    

    I iterate in reverse so the indices that are removed are stable as you iterate and remove items.

How many English words
do you know?
Test your English vocabulary size, and measure
how many words do you know
Online Test
Powered by Examplum