Today, I’m going to show you how to add a RecyclerView inside another RecyclerView.

In this example that I’m about to show you, we have categories, and inside the categories, we have a different number of subcategories.

Each subcategory has a RecyclerView and shows the items horizontally.

Every item has an ImageView to show the color and a TextView for the name of the color.

Prepare the Data and Models

Before we start, create an empty Kotlin file and name it Colors (Colors.kt) and paste the following data inside:

data class Colors( var objectsArray: ArrayList<Vertical_RVModel> = arrayListOf( Vertical_RVModel( "Category #1", arrayListOf("SubCategory #1.1", "SubCategory #1.2"), arrayListOf( // SubCategory #1.1 arrayListOf( Horizontal_RVModel("#DA70D6", "Orchid"), Horizontal_RVModel("#FA8072", "Salmon"), Horizontal_RVModel("#FDF5E6", "Old Lace"), Horizontal_RVModel("#00FFFF", "Aqua"), Horizontal_RVModel("#2E8B57", "Sea Green") ), // SubCategory #1.2 arrayListOf( Horizontal_RVModel("#2F4F4F", "Dark Slate Gray"), Horizontal_RVModel("#F0FFF0", "Honeydew"), Horizontal_RVModel("#DCDCDC", "Gainsboro") ) ) ), Vertical_RVModel( "Category #2", arrayListOf("SubCategory #2.1"), arrayListOf( // SubCategory #2.1 arrayListOf( Horizontal_RVModel("#FFE4B5", "Moccasin"), Horizontal_RVModel("#AFEEEE", "Pale Turquoise"), Horizontal_RVModel("#9400D3", "Dark Violet"), Horizontal_RVModel("#3CB371", "Medium Sea Green") ) ) ), Vertical_RVModel( "Category #3", arrayListOf("SubCategory #3.1", "SubCategory #3.2"), arrayListOf( // SubCategory #3.1 arrayListOf( Horizontal_RVModel("#FF6347", "Tomato"), Horizontal_RVModel("#4682B4", "Steel Blue"), Horizontal_RVModel("#778899", "Light Slate Gray"), Horizontal_RVModel("#191970", "Midnight Blue"), Horizontal_RVModel("#A52A2A", "Brown") ), // SubCategory #3.2 arrayListOf( Horizontal_RVModel("#FFF8DC", "Cornsilk"), Horizontal_RVModel("#FF00FF", "Magenta"), Horizontal_RVModel("#7CFC00", "Lawn Green"), Horizontal_RVModel("#000000", "Black"), Horizontal_RVModel("#00BFFF", "Deep Sky Blue"), Horizontal_RVModel("#6495ED", "Cornflower Blue"), Horizontal_RVModel("#FF8C00", "Dark Orange"), Horizontal_RVModel("#20B2AA", "Light Sea Green"), Horizontal_RVModel("#FFC0CB", "Pink") ) ) ), Vertical_RVModel( "Category #4", arrayListOf("SubCategory #4.1", "SubCategory #4.2"), arrayListOf( // SubCategory #4.1 arrayListOf( Horizontal_RVModel("#DDA0DD", "Plum"), Horizontal_RVModel("#FFF5EE", "Seashell"), Horizontal_RVModel("#FFDEAD", "Navajo White"), Horizontal_RVModel("#00FF00", "Lime"), Horizontal_RVModel("#F0E68C", "Khaki") ), // SubCategory #4.2 arrayListOf( Horizontal_RVModel("#FAEBD7", "Antique White"), Horizontal_RVModel("#C71585", "Medium Violet Red"), Horizontal_RVModel("#6B8E23", "Olive Drab"), Horizontal_RVModel("#FF4500", "Orange Red"), Horizontal_RVModel("#FFF0F5", "Lavender Blush") ) ) ), Vertical_RVModel( "Category #5", arrayListOf("SubCategory #5.1", "SubCategory #5.2"), arrayListOf( // SubCategory #5.1 arrayListOf( Horizontal_RVModel("#9966CC", "Amethyst") ), // SubCategory #5.2 arrayListOf( Horizontal_RVModel("#7B68EE", "Medium Slate Blue"), Horizontal_RVModel("#800000", "Maroon"), Horizontal_RVModel("#FFA07A", "Light Salmon"), Horizontal_RVModel("#E6E6FA", "Lavender"), Horizontal_RVModel("#FFE4C4", "Bisque") ) ) ) ) )

In Vertical RecyclerView there are 3 things you need:

Category Name

SubCategory Name

List of Colors

Create a new Kotlin file, name it Vertical_RVModel, and paste inside the following code:

data class Vertical_RVModel( val category: String, val subcategory: ArrayList<String>, val colors: ArrayList<ArrayList<Horizontal_RVModel>> )

The List of Colors has another model, which contains for each item:

Hex code for Color

Color name

Create a new Kotlin file, name it Horizontal_RVModel, and paste inside the following code:

data class Horizontal_RVModel ( val color: String, val name: String )

Creating the Vertical RecyclerView

Go to your Activity‘s layout, in this example is activity_main.xml and add a RecyclerView

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimaryDark" tools:context=".MainActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/vertical_rv" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>

If you don’t have added the RecyclerView dependency in your project, go to build.gradle of your app and add the following line to your dependencies:

dependencies { // ... implementation 'androidx.recyclerview:recyclerview:1.1.0' // ... }

After you added the RecyclerView, create a layout for the items.

Each item has the TextView for the Subcategory, and the Horizontal RecyclerView that we’ll add later.

Create a .xml file inside the layout folder (res > layout), name it vertical_item and paste the following code:

<?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"> <TextView android:id="@+id/subcategory_text" android:layout_width="match_parent" android:layout_height="20dp" android:background="@color/colorPrimaryDark" android:gravity="center_vertical" android:paddingStart="16dp" android:paddingLeft="16dp" android:paddingEnd="0dp" android:paddingRight="0dp" android:text="SubCategory #1" android:textColor="@android:color/white" android:textSize="16sp" android:textStyle="bold" /> </LinearLayout>

In this tutorial, we’re going to use Sections, but RecyclerView doesn’t have this feature build-in.

So we have to rely on the 3rd party library called SectionedRecyclerViewAdapter for this thing, which will make the process painless.

Go to your dependencies again and add:

dependencies { // ... implementation 'io.github.luizgrp.sectionedrecyclerviewadapter:sectionedrecyclerviewadapter:3.1.0' // ... }

Now let’s make the layout for the header for each section.

Create a .xml file inside the layout folder (res > layout), name it vertical_header, and paste inside the following code:

<?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="35dp" android:orientation="vertical"> <TextView android:id="@+id/vertical_category_text" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/colorPrimary" android:gravity="center_vertical" android:paddingStart="10dp" android:paddingLeft="10dp" android:paddingEnd="10dp" android:paddingRight="10dp" android:text="Category #1" android:textColor="@android:color/white" android:textSize="18sp" android:textStyle="bold" /> </LinearLayout>

It’s time now to make the adapter for this RecyclerView.

Create a new Kotlin file and name it Vertical_RVAdapter.

The adapter will get as parameters the category name, the subcategories for this section, and the list of colors for each subcategory.

The SectionedRecyclerViewAdapter we added before, will give as the ability to set a layout for the header and item for each section.

Also, we’ll be able to create the ViewHolders and BindViewHolders for them.

class Vertical_RVAdapter(private val category: String, private val subcategory: ArrayList<String>, private val colors: ArrayList<ArrayList<Horizontal_RVModel>>) : Section(SectionParameters.builder().itemResourceId(R.layout.vertical_item).headerResourceId(R.layout.vertical_header).build()) { // Size of Subcategories in each Section override fun getContentItemsTotal(): Int { return subcategory.size } class SectionViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView) class ItemViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView) override fun getHeaderViewHolder(view: View): RecyclerView.ViewHolder { return SectionViewHolder(view) } override fun getItemViewHolder(view: View): RecyclerView.ViewHolder { return ItemViewHolder(view) } override fun onBindHeaderViewHolder(holder: RecyclerView.ViewHolder) { holder.itemView.vertical_category_text.setTextColor(Color.WHITE) holder.itemView.vertical_category_text.text = category } override fun onBindItemViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val itemHolder = holder as ItemViewHolder // Subcategory itemHolder.itemView.subcategory_text.text = subcategory[position] } }

Creating the Horizontal RecyclerView

Go to the vertical_item.xml file you created before and under the TextView for the Subcategory, add a RecyclerView:

<?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"> <TextView android:id="@+id/subcategory_text" android:layout_width="match_parent" android:layout_height="20dp" android:background="@color/colorPrimaryDark" android:gravity="center_vertical" android:paddingStart="16dp" android:paddingLeft="16dp" android:paddingEnd="0dp" android:paddingRight="0dp" android:text="SubCategory #1" android:textColor="@android:color/white" android:textSize="16sp" android:textStyle="bold" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/horizontal_rv" android:layout_width="match_parent" android:layout_height="185dp" android:background="@color/colorPrimaryDark" android:clipToPadding="false" android:paddingStart="4dp" android:paddingLeft="4dp" android:paddingEnd="4dp" android:paddingRight="4dp"/> </LinearLayout>

As you see, we added 4dp padding in the left and right side of the RecyclerView, together with the android:clipToPadding=”false” allows us to have space at the beginning and end of the RecyclerView.

Create a new .xml file inside the layout folder (res > layout), name it horizontal_item, and paste the following code:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="180dp" android:background="@color/colorPrimaryDark" android:orientation="vertical"> <ImageView android:id="@+id/item_image" android:layout_width="150dp" android:layout_height="150dp" android:background="#FA8072" android:scaleType="centerCrop" /> <TextView android:id="@+id/item_title" android:layout_width="match_parent" android:layout_height="30dp" android:ellipsize="end" android:gravity="center" android:maxLines="1" android:text="Title" android:textColor="@android:color/white" android:textStyle="bold" /> </LinearLayout>

Now, create a new Kotlin file and name it Horizontal_RVAdapter, for the adapter of this RecyclerView.

In this adapter, we create a method setColorsData that takes the List of Colors from the Vertical_RVAdapter and updates the adapter.

Also, we create a setRowIndex method to get the number of the row inside the Horizontal RecyclerView.

class Horizontal_RVAdapter(private var mContext: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { private var itemsList: ArrayList<Horizontal_RVModel> = ArrayList() private var mRowIndex = -1 fun setColorsData(data: ArrayList<Horizontal_RVModel>) { if (itemsList != data) { itemsList = data notifyDataSetChanged() } } fun setRowIndex(index: Int) { mRowIndex = index } class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val itemView = LayoutInflater.from(mContext).inflate(R.layout.horizontal_item, parent, false) return ItemViewHolder(itemView) } override fun getItemCount(): Int { return itemsList.size } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { holder.itemView.item_image.setBackgroundColor(Color.parseColor(itemsList[position].color)) holder.itemView.item_title.text = itemsList[position].name holder.itemView.setOnClickListener { Log.d( "TAG", "You pressed the item in the category row $mRowIndex and position $position" ) } } }

Connecting the RecyclerViews

Now we’ve done both RecyclerViews it’s time to connect the Horizontal with the Vertical RecyclerView.

Go to your Vertical_RVAdapter and initialize the Horizontal RecyclerView in the ItemViewHolder and on onBindItemViewHolder pass the list of colors and the number of the current row:

class Vertical_RVAdapter(private val category: String, private val subcategory: ArrayList<String>, private val colors: ArrayList<ArrayList<Horizontal_RVModel>>) : Section(SectionParameters.builder().itemResourceId(R.layout.vertical_item).headerResourceId(R.layout.vertical_header).build()) { // ... class ItemViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView) { private val horizontalRecyclerView: RecyclerView val horizontalAdapter: Horizontal_RVAdapter init { val context = itemView.context horizontalRecyclerView = itemView.findViewById(R.id.horizontal_rv) horizontalRecyclerView?.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) horizontalRecyclerView?.addItemDecoration( EqualSpacingItemDecoration( 8, EqualSpacingItemDecoration.HORIZONTAL ) ) horizontalAdapter = Horizontal_RVAdapter(context) horizontalRecyclerView.adapter = horizontalAdapter } } // ... override fun onBindItemViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val itemHolder = holder as ItemViewHolder // ... // Color items itemHolder.horizontalAdapter.setColorsData(colors[position]) // Pass the current row itemHolder.horizontalAdapter.setRowIndex(position) } }

In this example, I’m using the class EqualSpacingItemDecoration that makes equal spacing between items.

If you want to use it on your project too, create a new Kotlin file, name it EqualSpacingItemDecoration, and paste the following code inside:

class EqualSpacingItemDecoration( private val spacing: Int, private var displayMode: Int ) : ItemDecoration() { override fun getItemOffsets( outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State ) { val position = parent.getChildViewHolder(view).adapterPosition val itemCount = state.itemCount val layoutManager = parent.layoutManager setSpacingForDirection(outRect, layoutManager, position, itemCount) } private fun setSpacingForDirection( outRect: Rect, layoutManager: RecyclerView.LayoutManager?, position: Int, itemCount: Int ) { // Resolve display mode automatically if (displayMode == -1) { displayMode = resolveDisplayMode(layoutManager) } when (displayMode) { HORIZONTAL -> { outRect.left = spacing outRect.right = if (position == itemCount - 1) spacing else 0 outRect.top = spacing outRect.bottom = spacing } VERTICAL -> { outRect.left = spacing outRect.right = spacing outRect.top = spacing outRect.bottom = if (position == itemCount - 1) spacing else 0 } GRID -> if (layoutManager is GridLayoutManager) { val cols = layoutManager.spanCount val rows = itemCount / cols outRect.left = spacing outRect.right = if (position % cols == cols - 1) spacing else 0 outRect.top = spacing outRect.bottom = if (position / cols == rows - 1) spacing else 0 } } } private fun resolveDisplayMode(layoutManager: RecyclerView.LayoutManager?): Int { if (layoutManager is GridLayoutManager) return GRID return if (layoutManager!!.canScrollHorizontally()) HORIZONTAL else VERTICAL } companion object { const val HORIZONTAL = 0 private const val VERTICAL = 1 private const val GRID = 2 } }

Final Step: Setting up Vertical RecyclerView and Adding Data

Go to your Activity‘s file, set RecyclerView‘s layout to Vertical, and pass the SectionedRecyclerViewAdapter() to RecyclerView’s(vertical_rv) adapter.

class MainActivity : AppCompatActivity() { private val sectionAdapter = SectionedRecyclerViewAdapter() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) vertical_rv.layoutManager = layoutManager vertical_rv.adapter = sectionAdapter // ... } }

Last, create an AsyncTask class, add the data, and update the adapter.

class MainActivity : AppCompatActivity() { // ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //... FetchDemoData(this, vertical_rv, sectionAdapter).execute() } private class FetchDemoData internal constructor(mContext: MainActivity, verticalRecyclerView: RecyclerView, sectionAdapter: SectionedRecyclerViewAdapter) : AsyncTask<Void, Void, Void>() { private val activityReference: WeakReference<MainActivity> = WeakReference(mContext) var verticalRv = verticalRecyclerView var secAdapter = sectionAdapter val colors = Colors().objectsArray override fun doInBackground(vararg params: Void): Void? { for (color in colors) { secAdapter.addSection( Vertical_RVAdapter( color.category, color.subcategory, color.colors ) ) } return null } override fun onPostExecute(result: Void?) { // get a reference to the activity if it is still there val activity = activityReference.get() if (activity == null || activity.isFinishing) return verticalRv.adapter?.notifyDataSetChanged() } } }

You can find the final project here

If you have any questions, please feel free to leave a comment below