Kotlin RecyclerView: How to Create Expandable Items

The RecyclerView is a fundamental and versatile component in Android development, widely used for displaying large sets of data in a scrollable list or grid. While RecyclerView is powerful out of the box, you often need to add custom behavior, such as creating expandable items. Expandable items can significantly improve the user experience by allowing users to show or hide details within a list. In this comprehensive guide, we will walk you through creating expandable items in a RecyclerView using Kotlin with XML layouts.

Understanding the Basics

Before diving into the code, it’s important to have a basic understanding of RecyclerView components:

  • RecyclerView: The main view that displays the list of items.
  • Adapter: Responsible for providing data to the RecyclerView and creating ViewHolder instances.
  • ViewHolder: Holds the view instances for each item, improving performance by reusing views.
  • LayoutManager: Manages the layout of items within the RecyclerView (e.g., LinearLayoutManager, GridLayoutManager).

Setting up the Project

First, let’s set up a new Android project in Kotlin and add the necessary dependencies. Open Android Studio and create a new project with an empty activity. Then, add the RecyclerView dependency to your app’s build.gradle file:

dependencies {
    implementation("androidx.recyclerview:recyclerview:1.2.1")
    implementation("androidx.recyclerview:recyclerview-selection:1.1.0")
    // Other dependencies
}

Sync your project after adding the dependency to make it available for use.

Creating the Layouts

Next, we’ll define the layout files required for our RecyclerView and the individual items.

1. RecyclerView Layout (activity_main.xml)

This layout will contain the RecyclerView widget:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

2. Item Layout (item_expandable.xml)

This layout represents each item in the RecyclerView, including a header and expandable details:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp">

    <LinearLayout
        android:id="@+id/headerLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center_vertical">

        <TextView
            android:id="@+id/titleTextView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textSize="18sp"
            android:textStyle="bold"
            android:text="Item Title"/>

        <ImageView
            android:id="@+id/arrowImageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_arrow_down"
            android:contentDescription="Expand/Collapse"/>

    </LinearLayout>

    <TextView
        android:id="@+id/descriptionTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Item Description"
        android:visibility="gone"/>

</LinearLayout>

Note:

  • The headerLayout LinearLayout contains the title and an arrow icon, which acts as the header that users click to expand/collapse the item.
  • The descriptionTextView TextView holds the detailed information and is initially set to android:visibility="gone" to hide it.

Creating the Data Model

Let’s create a simple data class to represent the items in the RecyclerView. Create a Kotlin class named ExpandableItem:

data class ExpandableItem(
    val title: String,
    val description: String,
    var isExpanded: Boolean = false
)

Creating the Adapter

The adapter is a crucial component for providing data and handling item views in the RecyclerView. Here’s how to create an adapter for the expandable items:

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import kotlin.reflect.KFunction1

class ExpandableAdapter(private val itemList: List) :
    RecyclerView.Adapter() {

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

    override fun onBindViewHolder(holder: ExpandableViewHolder, position: Int) {
        val item = itemList[position]
        holder.bind(item)
    }

    override fun getItemCount(): Int = itemList.size

    inner class ExpandableViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val titleTextView: TextView = itemView.findViewById(R.id.titleTextView)
        private val descriptionTextView: TextView = itemView.findViewById(R.id.descriptionTextView)
        private val arrowImageView: ImageView = itemView.findViewById(R.id.arrowImageView)
        private val headerLayout: LinearLayout = itemView.findViewById(R.id.headerLayout)

        fun bind(item: ExpandableItem) {
            titleTextView.text = item.title
            descriptionTextView.text = item.description

            // Initially set visibility based on isExpanded state
            descriptionTextView.visibility = if (item.isExpanded) View.VISIBLE else View.GONE

            // Rotate arrow based on isExpanded state
            val rotationAngle = if (item.isExpanded) 180f else 0f
            arrowImageView.rotation = rotationAngle

            // Handle item click to expand/collapse
            headerLayout.setOnClickListener {
                item.isExpanded = !item.isExpanded
                descriptionTextView.visibility = if (item.isExpanded) View.VISIBLE else View.GONE

                // Animate arrow rotation
                arrowImageView.animate().rotation(if (item.isExpanded) 180f else 0f).start()
            }
        }
    }
}

Key parts of the ExpandableAdapter:

  • onCreateViewHolder: Inflates the layout for each item and returns a ViewHolder.
  • onBindViewHolder: Binds the data to the views within the ViewHolder.
  • ExpandableViewHolder:
    • Retrieves views from item_expandable.xml using findViewById.
    • Binds the data to the views, setting the title and description.
    • Handles the click listener on the header to toggle the visibility of the description and animate the arrow.
    • Sets the initial visibility of the description based on the isExpanded state.
  • Click Listener: Toggles the visibility of the description TextView and rotates the arrow based on the expanded state.

Setting up the Activity

Now, let’s set up the main activity to use the RecyclerView and the adapter. Open your MainActivity.kt and add the following code:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: ExpandableAdapter
    private val itemList = mutableListOf<ExpandableItem>()

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

        recyclerView = findViewById(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)

        // Sample data
        itemList.add(ExpandableItem("Item 1", "Description for Item 1"))
        itemList.add(ExpandableItem("Item 2", "Description for Item 2"))
        itemList.add(ExpandableItem("Item 3", "Description for Item 3"))

        adapter = ExpandableAdapter(itemList)
        recyclerView.adapter = adapter
    }
}

Explanation:

  • Initialization: Initializes the RecyclerView, adapter, and a list of ExpandableItem objects.
  • RecyclerView Setup: Sets the layout manager and adapter for the RecyclerView.
  • Data: Creates a list of ExpandableItem objects to populate the RecyclerView.

Adding Arrow Rotation Animation

To make the expansion and collapse effect more visually appealing, you can add a rotation animation to the arrow icon. Add the following lines to the bind method in your ExpandableAdapter:

arrowImageView.animate().rotation(if (item.isExpanded) 180f else 0f).start()

Handling Configuration Changes

To properly handle configuration changes, such as screen rotations, ensure that the expanded state of the items is preserved. You can use the onSaveInstanceState and onRestoreInstanceState methods to save and restore the state of the RecyclerView.

Improving Performance

For large datasets, consider using DiffUtil to update the RecyclerView efficiently when the data changes. DiffUtil helps to minimize the number of updates by calculating the differences between the old and new lists.

Conclusion

Creating expandable items in a RecyclerView can significantly enhance the user experience in your Android applications. By using Kotlin and XML layouts, you can efficiently display large datasets while allowing users to focus on relevant details. This guide covered the essential steps for setting up your project, creating layouts, defining data models, implementing the adapter, and animating the UI. With this knowledge, you can customize and extend your RecyclerView implementation to meet the specific needs of your application.