Skip to main content

Working with RecyclerView

1. Creating RecyclerView Adapter

Property Description Values
onCreateViewHolder Creates a new ViewHolder for the RecyclerView RecyclerView.ViewHolder
onBindViewHolder Binds data to the ViewHolder at the specified position RecyclerView.ViewHolder, Int
getItemCount Returns the total number of items in the data set Int

Example: Basic RecyclerView Adapter

class MyAdapter(private val items: List<String>) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.textView)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_layout, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.textView.text = items[position]
    }

    override fun getItemCount(): Int = items.size
}
Note: The adapter must extend RecyclerView.Adapter and implement the required methods.

2. Implementing ViewHolder Pattern

Component Description Usage
ViewHolder Encapsulates the view elements of a single item in the list Extend RecyclerView.ViewHolder
onCreateViewHolder Creates a new ViewHolder instance Called by RecyclerView to inflate layout
onBindViewHolder Binds data to the ViewHolder's views Called by RecyclerView to populate item views

Example: Basic ViewHolder Implementation

class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val titleTextView: TextView = itemView.findViewById(R.id.title)
    val descriptionTextView: TextView = itemView.findViewById(R.id.description)
}

class MyAdapter(private val items: List<MyItem>) : RecyclerView.Adapter<MyViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_layout, parent, false)
        return MyViewHolder(view)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val item = items[position]
        holder.titleTextView.text = item.title
        holder.descriptionTextView.text = item.description
    }

    override fun getItemCount(): Int = items.size
}
Note: The ViewHolder pattern prevents unnecessary view lookups and improves performance by caching view references.

3. Binding Data to Views

Property Description Example
itemView Reference to the root view of the item layout val itemView = findViewById<View>(R.id.item_layout)
binding View binding instance for the item layout val binding = ItemLayoutBinding.bind(itemView)

Example: Binding Data to Views in RecyclerView

class MyAdapter(private val dataList: List<DataItem>) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

    inner class ViewHolder(val binding: ItemLayoutBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = dataList[position]
        holder.binding.textView.text = item.title
        holder.binding.imageView.setImageResource(item.imageResId)
    }

    override fun getItemCount(): Int = dataList.size
}
Note: Using view binding improves performance and type safety compared to traditional findViewById calls.

4. Setting Layout Manager

Property Description Values
layoutManager Sets the layout manager for the RecyclerView LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager

Example: Setting LinearLayoutManager

val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)

Example: Setting GridLayoutManager

val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = GridLayoutManager(this, 2)

Example: Setting StaggeredGridLayoutManager

val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)

5. Handling Item Click Events

Event Type Description Handler Method Notes
onBindViewHolder Called when a view holder is bound to data ViewHolder.itemView.setOnClickListener { ... } Most common approach for handling clicks
ViewHolder View holder instance with access to item view holder.itemView.setOnClickListener { ... } Use holder.adapterPosition to get item index

Example: Click Listener in ViewHolder

class MyAdapter(
    private val items: List<String>
) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(item: String) {
            itemView.setOnClickListener {
                // Handle click event
                val position = adapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    // Perform action with item at position
                    println("Clicked item: $item at position $position")
                }
            }
        }
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(items[position])
    }

    // ... other required methods
}
Note: Always check adapterPosition != RecyclerView.NO_POSITION before using the position to avoid errors.

Best Practices for Click Handling

  • Use adapterPosition to get the actual item index
  • Check for RecyclerView.NO_POSITION to prevent errors
  • Handle click events inside onBindViewHolder or in the ViewHolder
  • Consider using setOnLongClickListener for long-press actions

6. Using DiffUtil for Updates

DiffUtil is a utility class in Android that helps calculate the difference between two lists and efficiently update a RecyclerView with minimal performance impact.

Example: Using DiffUtil to Update RecyclerView

class UserDiffCallback(
    private val oldList: List<User>,
    private val newList: List<User>
) : DiffUtil.Callback() {
    override fun getOldListSize(): Int = oldList.size
    override fun getNewListSize(): Int = newList.size

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition].id == newList[newItemPosition].id
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition] == newList[newItemPosition]
    }
}

class UserAdapter(private var users: List<User>) : RecyclerView.Adapter<UserViewHolder>() {
    fun updateUsers(newUsers: List<User>) {
        val diffCallback = UserDiffCallback(users, newUsers)
        val diffResult = DiffUtil.calculateDiff(diffCallback)

        users = newUsers
        diffResult.dispatchUpdatesTo(this)
    }

    // ... other adapter methods
}
Method Description Notes
getOldListSize() Returns the size of the old list Required override
getNewListSize() Returns the size of the new list Required override
areItemsTheSame() Checks if items at given positions are the same Used to identify moved or deleted items
areContentsTheSame() Checks if contents of items at given positions are the same Used to determine if an item needs to be updated visually
Note: DiffUtil is more efficient than calling notifyDataSetChanged() because it calculates only the minimal set of changes needed to update the UI.
Warning: Make sure areItemsTheSame correctly identifies items by a unique identifier (e.g., ID) to avoid incorrect updates.

7. Implementing ListAdapter

ListAdapter is a powerful utility in Android development that simplifies the process of binding data to a RecyclerView. It automatically handles diffing and updates, making it more efficient than manually managing data changes.

Property Description Notes
ListAdapter Base class for adapting data to RecyclerView Extends Adapter and handles automatic diffing
submitList() Updates the list with new data Triggers diff calculation and UI update
DiffUtil.ItemCallback Defines how items are compared for changes Required to calculate differences efficiently

Example: Basic ListAdapter Implementation

class MyAdapter : ListAdapter<MyItem, MyViewHolder>(MyDiffCallback()) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        // Create and return ViewHolder
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bind(getItem(position))
    }
}

class MyDiffCallback : DiffUtil.ItemCallback<MyItem>() {
    override fun areItemsTheSame(old: MyItem, new: MyItem): Boolean = old.id == new.id
    override fun areContentsTheSame(old: MyItem, new: MyItem): Boolean = old == new
}

Example: Using submitList()

val adapter = MyAdapter()
adapter.submitList(newList)
Note: ListAdapter uses DiffUtil under the hood to efficiently calculate changes between lists, reducing unnecessary UI updates.
Warning: Ensure that DiffUtil.ItemCallback correctly identifies items to avoid incorrect UI updates.

8. Adding Item Decorations

Item decorations provide visual enhancements to RecyclerView items, such as dividers, backgrounds, or spacing. They are drawn on top of the item views.

Property Description Usage
RecyclerView.ItemDecoration Base class for item decorations Extend to customize appearance
getItemOffsets() Defines space around item Modify layout margins
onDraw() Draws decoration on top of items Custom drawing logic
onDrawOver() Draws decoration behind items Overlay effects

Example: Divider Decoration

class DividerDecoration : RecyclerView.ItemDecoration() {
    private val divider = ContextCompat.getDrawable(context, R.drawable.divider)

    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        val left = parent.paddingLeft.toFloat()
        val right = (parent.width - parent.paddingRight).toFloat()

        for (i in 0 until parent.childCount) {
            val child = parent.getChildAt(i)
            val params = child.layoutParams as RecyclerView.LayoutParams

            val top = child.bottom + params.bottomMargin
            val bottom = top + divider?.intrinsicHeight!!

            divider.setBounds(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
            divider.draw(c)
        }
    }
}
Note: Use onDrawOver() to draw decorations behind items, and onDraw() for those in front.

9. Implementing Swipe to Dismiss

Feature Description Implementation
Swipe Gesture Allows users to swipe items left or right to delete or archive them. Uses ItemTouchHelper with SimpleCallback.
Swipe Direction Supports left-to-right (e.g., delete) and right-to-left (e.g., archive). Configured via Swiped flags in onMove.

Example: Swipe to Dismiss Implementation

class SwipeToDeleteCallback(
    private val adapter: MyAdapter
) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {

    override fun onMove(
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        target: RecyclerView.ViewHolder
    ): Boolean = false

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        val position = viewHolder.adapterPosition
        if (direction == ItemTouchHelper.LEFT) {
            adapter.removeItem(position)
        }
    }
}
Note: Ensure ItemTouchHelper is attached to the RecyclerView in your activity or fragment.

Setup Steps

  1. Create a SwipeToDeleteCallback class extending ItemTouchHelper.SimpleCallback.
  2. Override onSwiped to handle item removal.
  3. Attach the callback to the RecyclerView using ItemTouchHelper.

10. Using Multiple View Types

View Type Description Usage
VIEW_TYPE_HEADER Represents a header item in the list Used for displaying section headers or titles
VIEW_TYPE_ITEM Represents a regular list item Used for displaying content items in the list
VIEW_TYPE_FOOTER Represents a footer item in the list Used for displaying footers or summary information

Example: Defining View Types

class MyAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    companion object {
        const val VIEW_TYPE_HEADER = 0
        const val VIEW_TYPE_ITEM = 1
        const val VIEW_TYPE_FOOTER = 2
    }

    override fun getItemViewType(position: Int): Int {
        return when (position) {
            0 -> VIEW_TYPE_HEADER
            items.size + 1 -> VIEW_TYPE_FOOTER
            else -> VIEW_TYPE_ITEM
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            VIEW_TYPE_HEADER -> HeaderViewHolder(
                LayoutInflater.from(parent.context)
                    .inflate(R.layout.header_item, parent, false)
            )
            VIEW_TYPE_ITEM -> ItemViewHolder(
                LayoutInflater.from(parent.context)
                    .inflate(R.layout.list_item, parent, false)
            )
            VIEW_TYPE_FOOTER -> FooterViewHolder(
                LayoutInflater.from(parent.context)
                    .inflate(R.layout.footer_item, parent, false)
            )
            else -> throw IllegalArgumentException("Unknown view type: $viewType")
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is HeaderViewHolder -> holder.bind(headerData)
            is ItemViewHolder -> holder.bind(items[position - 1])
            is FooterViewHolder -> holder.bind(footerData)
        }
    }

    override fun getItemCount(): Int = items.size + 2
}

Example: ViewHolder Implementations

class HeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val titleText = itemView.findViewById<TextView>(R.id.title_text)

    fun bind(data: HeaderData) {
        titleText.text = data.title
    }
}

class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val itemText = itemView.findViewById<TextView>(R.id.item_text)
    private val itemImage = itemView.findViewById<ImageView>(R.id.item_image)

    fun bind(data: ItemData) {
        itemText.text = data.title
        itemImage.setImageResource(data.imageResId)
    }
}

class FooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val footerText = itemView.findViewById<TextView>(R.id.footer_text)

    fun bind(data: FooterData) {
        footerText.text = data.message
    }
}
Note: When using multiple view types, ensure that the getItemViewType method returns consistent view type values for each position to avoid incorrect view recycling.

11. Implementing Pagination

Example: Basic Pagination Setup

class PaginationAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private val items = mutableListOf<String>()
    
    fun addItems(newItems: List<String>) {
        items.addAll(newItems)
        notifyDataSetChanged()
    }
    
    fun loadMore() {
        // Simulate loading more items
        val newItems = (1..10).map { "Item ${items.size + it}" }
        addItems(newItems)
    }
}
Property Description Usage
RecyclerView UI component for displaying large datasets efficiently Used to display paginated content in a scrollable list
Adapter Connects data to RecyclerView Handles item creation and binding for each page
LoadMore Trigger to fetch next page of data Implement in scroll listener or button click

Workflow: Implementing Pagination

  1. Add scroll listener to RecyclerView
  2. Detect when user reaches end of list
  3. Fetch next page of data from API
  4. Update adapter with new items
  5. Notify RecyclerView of data changes
Note: Use RecyclerView.Adapter.notifyItemRangeInserted() for better performance when adding items to the end of the list.

Comparison: Pagination Approaches

  • Scroll-based: Load more when user scrolls to bottom
  • Button-based: User taps "Load More" button to fetch next page
  • Infinite: Automatically load pages as needed without user action

12. Handling Drag and Drop

Feature Description Notes
ItemTouchHelper Class for handling drag and drop gestures Part of AndroidX library
OnItemTouchListener Interface to detect touch events on RecyclerView items Used for custom drag handling

Example: Basic Drag and Drop Setup

class MainActivity : AppCompatActivity() {
    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: MyAdapter

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

        recyclerView = findViewById(R.id.recyclerView)
        adapter = MyAdapter(dataList)

        val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
            ItemTouchHelper.UP or ItemTouchHelper.DOWN,
            0
        ) {
            override fun onMove(
                recyclerView: RecyclerView,
                viewHolder: RecyclerView.ViewHolder,
                target: RecyclerView.ViewHolder
            ): Boolean {
                val fromPosition = viewHolder.adapterPosition
                val toPosition = target.adapterPosition
                adapter.moveItem(fromPosition, toPosition)
                return true
            }

            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
        })

        itemTouchHelper.attachToRecyclerView(recyclerView)
        recyclerView.adapter = adapter
    }
}
Note: The SimpleCallback constructor takes two parameters: dragFlags and swipeFlags. Use ItemTouchHelper.UP or ItemTouchHelper.DOWN for vertical drag.

Example: Custom Drag Handle

class MyAdapter(private val dataList: MutableList<String>) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val textView: TextView = itemView.findViewById(R.id.textView)
        val dragHandle: ImageView = itemView.findViewById(R.id.dragHandle)

        init {
            dragHandle.setOnTouchListener { _, event ->
                if (event.action == MotionEvent.ACTION_DOWN) {
                    val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
                        ItemTouchHelper.UP or ItemTouchHelper.DOWN,
                        0
                    ) {
                        override fun onMove(
                            recyclerView: RecyclerView,
                            viewHolder: RecyclerView.ViewHolder,
                            target: RecyclerView.ViewHolder
                        ): Boolean {
                            val fromPosition = viewHolder.adapterPosition
                            val toPosition = target.adapterPosition
                            dataList.add(toPosition, dataList.removeAt(fromPosition))
                            notifyItemMoved(fromPosition, toPosition)
                            return true
                        }

                        override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
                    })
                    itemTouchHelper.startDrag(this@ViewHolder)
                }
                false
            }
        }
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.textView.text = dataList[position]
    }

    override fun getItemCount(): Int = dataList.size
}

Key Concepts

  • ItemTouchHelper is the core component for drag and drop handling
  • Implement SimpleCallback to define drag/swipe behavior
  • Use onMove() to handle item reordering
  • Drag handles can be added for better UX

Was this article helpful?