RecylerView With Multiple ViewHolder Android

Pandiyan Mani
5 min readMar 27, 2023

In this topic I am going to create a view which contain header and n number of name and email as sub content below the header. You can see the final output how it will look once we done with our coding.

So Lets Jump on to the coding part .So we need sealed class which contain multiple data class so the name of the sealed class is DataModel which has two sub data class one is ItemsViewModel and another one is HeaderData

package com.student.recyleerviewproject


sealed class DataModel {
data class ItemsViewModel(val name: String, val email: String) : DataModel()
data class HeaderData(val title: String, val description: String) : DataModel()
}

what is sealed class?

A sealed class defines a set of subclasses within it . so each class can have different parameter.

Sealed classes ensure type safety by restricting the types to be matched at compile-time rather than at runtime.

To define a sealed class, just precede the class modifier with the sealed keyword. The sealed classes also have one another distinct feature, their constructors are protected by default. A sealed class is implicitly abstract and hence it cannot be instantiated.

Lets jump in to creating layout needed for this project

so first we will have activity_main.xml which holds RecylerView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
tools:context=".MainActivity">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</LinearLayout>

Second Comes the header_layout.xml which has heading and description inside a Card View.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="wrap_content"
android:orientation="vertical">

<!--parent CardView-->
<androidx.cardview.widget.CardView
android:id="@+id/cardview_one"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:elevation="5dp"
app:cardBackgroundColor="#ff0000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">

<!--LinearLayout inside the CardView-->
<!--This layout is accessed to create
toasts when this item is clicked-->
<LinearLayout
android:id="@+id/linearlayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">


<!--This layout only holds a TextView
inside a CardView-->

<TextView
android:id="@+id/heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:textColor="#fff"
android:textSize="20dp"
android:textStyle="italic"
tools:text="Lorem Ipsum is simply dummy text" />

<TextView
android:id="@+id/desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:textColor="#fff"
android:textSize="20dp"
android:textStyle="italic"
tools:text="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s" />

</LinearLayout>
</androidx.cardview.widget.CardView>

<!-- This is extra space given to maintain a
gap between two consecutive Views-->
<Space
android:layout_width="match_parent"
android:layout_height="10dp" />

</LinearLayout>

And the final layout is item_list.xml which holds the sub item view that is name and email.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10sp"
android:orientation="vertical">

<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="20sp"
android:textStyle="italic"
tools:text="Name" />

<TextView
android:id="@+id/email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="20sp"
android:textStyle="italic"
tools:text="Email Id" />

</LinearLayout>

So we are done with layout part lets see the coding things

Lets first create a CustomAdapter.kt class you can see it take input List<DataModel> as a parameter. Next we create a ViewHolder class

class CustomAdapter(val mList: List<DataModel>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
}
class ViewHolder(ItemView: View) : RecyclerView.ViewHolder(ItemView) {
private fun bindHeaderData(item: DataModel.HeaderData) {
val heading: TextView = itemView.findViewById(R.id.heading)
val desc: TextView = itemView.findViewById(R.id.desc)

heading.text = item.title
desc.text = item.description
}

private fun bindItemsViewModel(item: DataModel.ItemsViewModel) {
val name: TextView = itemView.findViewById(R.id.name)
val email: TextView = itemView.findViewById(R.id.email)

name.text = item.name
email.text = item.email
}

fun bind(dataModel: DataModel) {
when (dataModel) {
is DataModel.HeaderData -> bindHeaderData(dataModel)
is DataModel.ItemsViewModel -> bindItemsViewModel(dataModel)
}
}
}

bindHeaderData() → is a method with hold view connected to HeaderData so inside that we have heading and desc TextView initialized.

bindItemsViewModel() → is a method with hold view connected to ItemsViewModel so inside that we have name and email TextView initialized.

so final one is bind() → method which decide which method has to be called on the above two methods weather bindHeaderData() or bindItemsViewModel()

Next comes the onCreateViewHolder() override function which decides the viewType.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {

val layout = when (viewType) {
TYPE_HEADER -> R.layout.header_layout
TYPE_ITEM -> R.layout.item_list
else -> throw IllegalArgumentException("Invalid view type")
}

val view = LayoutInflater.from(parent.context).inflate(layout, parent, false)
return ViewHolder(view)
}

so this view type is invoked from getItemViewType() override function based upon that onCreateViewHolder() function decides the layout.

override fun getItemViewType(position: Int): Int {
return when (mList[position]) {
is DataModel.HeaderData -> TYPE_HEADER
else -> TYPE_ITEM
}
}

so final override function is onBindViewHolder() which binds the data

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

Lets see the complete code for CustomAdapter.kt file

package com.student.recyleerviewproject

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class CustomAdapter(val mList: List<DataModel>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

class ViewHolder(ItemView: View) : RecyclerView.ViewHolder(ItemView) {
private fun bindHeaderData(item: DataModel.HeaderData) {
val heading: TextView = itemView.findViewById(R.id.heading)
val desc: TextView = itemView.findViewById(R.id.desc)

heading.text = item.title
desc.text = item.description
}

private fun bindItemsViewModel(item: DataModel.ItemsViewModel) {
val name: TextView = itemView.findViewById(R.id.name)
val email: TextView = itemView.findViewById(R.id.email)

name.text = item.name
email.text = item.email
}

fun bind(dataModel: DataModel) {
when (dataModel) {
is DataModel.HeaderData -> bindHeaderData(dataModel)
is DataModel.ItemsViewModel -> bindItemsViewModel(dataModel)
}
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {

val layout = when (viewType) {
TYPE_HEADER -> R.layout.header_layout
TYPE_ITEM -> R.layout.item_list
else -> throw IllegalArgumentException("Invalid view type")
}

val view = LayoutInflater.from(parent.context).inflate(layout, parent, false)
return ViewHolder(view)
}

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

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

override fun getItemViewType(position: Int): Int {
return when (mList[position]) {
is DataModel.HeaderData -> TYPE_HEADER
else -> TYPE_ITEM
}
}

companion object {
private const val TYPE_HEADER = 0
private const val TYPE_ITEM = 1
}

}

Lets see our MainActivity.kt File where we pass data to our adapter class.

package com.student.recyleerviewproject

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

class MainActivity : AppCompatActivity() {

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

val recyclerview = findViewById<RecyclerView>(R.id.recyclerview)

// this creates a vertical layout Manager
recyclerview.layoutManager = LinearLayoutManager(this)

val datas = ArrayList<DataModel>()
// data for Header Layout
datas.add(
DataModel.HeaderData(
title = "Heading",
description = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s"
)
)

// data for Sub Item Layout
for (i in 1..20) {
datas.add(
DataModel.ItemsViewModel(
name = "PANDIYAN $i", email = "TEST$i@GMAIL.COM"
)
)
}

val adapter = CustomAdapter(mList = datas)

recyclerview.adapter = adapter
}
}

Attached GitHub Source Code below:

https://github.com/Pandiyanmk/RecylerView-With-Multiple-ViewHolder-Android.git

--

--