Kotlin RecyclerView Sectioned Header Android Example is the topic of this article.
We will create recyclerview with sectioned headers in android in kotlin language.
Sometimes there are too many child items in the recycler view that we can categorize them for simplification.
For example, we can categorize vehicle manufacturer company by type of vehicles like bikes, cars, air crafts etc.
To sort child items by category we need to put category name first and then display items.
Thus, we need to create two different view types in recycler view. One is for category name which we call header and second is view is for normal child rows.
First of all, see the below video so that you can have idea regarding final output.
Now use the following moves for this example.
Step 1. Gradle Requirements
First of all, make a new project in android studio with Kotlin language.
We need to add some gradle lines in order to use recycler view and card view in our project.
So open your gradle file build.gradle(Module:app) and write the following lines in it
implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.android.support:cardview-v7:28.0.0'
First line is integrating recycler view classes in our project.
Similarly, second will help us to use card view classes.
Step 2. Drawable XML files
Now to differentiate header and child view layout we need to create two different layout XML files.
So navigate to the app->res->drawable directory and make a new XML file with name cardview.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true"> <shape android:shape="rectangle"> <padding android:left="4dp" android:top="4dp" android:right="4dp" android:bottom="4dp"/> <gradient android:startColor="#1cb73b" android:endColor="#29ba48" android:angle="270" /> <corners android:topLeftRadius="4dp" android:topRightRadius="4dp"/> </shape> </item> <item android:state_focused="false"> <shape android:shape="rectangle"> <padding android:left="4dp" android:top="4dp" android:right="4dp" android:bottom="4dp"/> <gradient android:startColor="#1cb724" android:endColor="#29ba41" android:angle="270" /> <corners android:topLeftRadius="4dp" android:topRightRadius="4dp" /> </shape> </item> </selector>
Above file will create the layout structure for header file on the recycler view.
It will use green gradient effect in the background.
Now make another XML file and give it name as cardview_child.xml
You should write the below lines in cardview_child.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true"> <shape android:shape="rectangle"> <padding android:left="4dp" android:top="4dp" android:right="4dp" android:bottom="4dp" /> <gradient android:startColor="#901cb7" android:endColor="#8529ba" android:angle="270" /> <corners android:topLeftRadius="4dp" android:topRightRadius="4dp"/> </shape> </item> <item android:state_focused="false" > <shape android:shape="rectangle"> <padding android:left="4dp" android:top="4dp" android:right="4dp" android:bottom="4dp" /> <gradient android:startColor="#8832c1" android:endColor="#8541c4" android:angle="270" /> <corners android:topLeftRadius="4dp" android:topRightRadius="4dp" /> </shape> </item> </selector>
This file is for making background structure of child row files of the recycler view.
Compiler will use violate color to paint the background of every child rows.
Step 3. Making Main Layout for RecyclerView
Go to app->res->layout folder. Inside layout folder, make a new file and set it’s name as rv_child.xml
This rv_child.xml file should contain the below code structure.
<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="wrap_content" tools:context=".MainActivity"> <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginBottom="10dp" card_view:cardCornerRadius="4dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/tv" android:height="50dp" android:background="@drawable/cardview_child" android:gravity="center_vertical" android:paddingLeft="10dp" android:text="Sub Item" android:textColor="#fff" android:textStyle="bold" android:textSize="30sp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/tvChild" android:background="#71b9d6" android:text="Bike" android:padding="10dp" android:textColor="#a72b2b" android:textSize="25sp"/> </LinearLayout> </android.support.v7.widget.CardView> </RelativeLayout>
This file contains two text views. It will use cardview_child.xml in the first text view.
Now make another XML file in the same folder. This time, name of the file should be rv_header.xml
This rv_header.xml is having the following coding lines.
<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="wrap_content" tools:context=".MainActivity"> <android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/card_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginBottom="10dp" card_view:cardCornerRadius="4dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/tv" android:height="50dp" android:background="@drawable/cardview" android:gravity="center_vertical" android:paddingLeft="10dp" android:text="Header : Vehicle Type" android:textColor="#fff" android:textStyle="bold" android:textSize="30sp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/tvHeader" android:background="#ece4e4" android:text="Bike" android:padding="10dp" android:textColor="#000" android:textSize="25sp"/> </LinearLayout> </android.support.v7.widget.CardView> </RelativeLayout>
It also has two text views. Among them, first text view has set cardview.xml in it’s background.
Thus, we have two different files. One is for header row and another is for child row of the recycler view.
Step 4. Adapter Class
Make a new Kotlin class with name like CustomAdapter.kt
Write down the below code lines in CustomAdapter.kt file.
import android.content.Context import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import java.util.ArrayList class CustomAdapter(private val context: Context, private val listItemArrayList: ArrayList<MainActivity.ListItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { private val inflater: LayoutInflater init { inflater = LayoutInflater.from(context) } override fun getItemCount(): Int { return listItemArrayList.size } override fun getItemViewType(position: Int): Int { return if (listItemArrayList[position].isHeader()) { LAYOUT_HEADER } else LAYOUT_CHILD } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val holder: RecyclerView.ViewHolder if (viewType == LAYOUT_HEADER) { val view = inflater.inflate(R.layout.rv_header, parent, false) holder = MyViewHolderHeader(view) } else { val view = inflater.inflate(R.layout.rv_child, parent, false) holder = MyViewHolderChild(view) } return holder } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder.itemViewType == LAYOUT_HEADER) { val vaultItemHolder = holder as MyViewHolderHeader vaultItemHolder.tvHeader.setText(listItemArrayList[position].getName()) } else { val vaultItemHolder = holder as MyViewHolderChild vaultItemHolder.tvchild.setText(listItemArrayList[position].getName()) } } internal inner class MyViewHolderHeader(itemView: View) : RecyclerView.ViewHolder(itemView) { var tvHeader: TextView init { tvHeader = itemView.findViewById<View>(R.id.tvHeader) as TextView } } internal inner class MyViewHolderChild(itemView: View) : RecyclerView.ViewHolder(itemView) { var tvchild: TextView init { tvchild = itemView.findViewById<View>(R.id.tvChild) as TextView } } companion object { private val LAYOUT_HEADER = 0 private val LAYOUT_CHILD = 1 } }
Adapter is the class which will fill the data in the recycler view.
See the below code of onCreateViewHolder() method.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val holder: RecyclerView.ViewHolder if (viewType == LAYOUT_HEADER) { val view = inflater.inflate(R.layout.rv_header, parent, false) holder = MyViewHolderHeader(view) } else { val view = inflater.inflate(R.layout.rv_child, parent, false) holder = MyViewHolderChild(view) } return holder }
In this method, compiler will check if the type of the row is header or child.
You can see the if condition in the above code which is doing this thing.
If the row is header then compiler will inflate rv_header.xml file, otherwise it will inflate rv_child.xml file.
Now attend the below code lines for onBindViewHolder() method.
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { if (holder.itemViewType == LAYOUT_HEADER) { val vaultItemHolder = holder as MyViewHolderHeader vaultItemHolder.tvHeader.setText(listItemArrayList[position].getName()) } else { val vaultItemHolder = holder as MyViewHolderChild vaultItemHolder.tvchild.setText(listItemArrayList[position].getName()) } }
onBindViewHolder() method will set the text value inside the text view.
Here, you can find one if loop to check whether the row is header or child.
Compiler will set the text in the text view of header (tvHeader) for header row and for child row it will set the text value in child text view(tvchild)
Last Step
Open up your activity_main.xml file and write the below text in it.
<?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="match_parent" android:background="#9e9191" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="15dp"/> </LinearLayout>
I have added just one recycler view in this main layout file.
Now write down the following code text in the MainActivity.kt file.
import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.view.View import java.util.ArrayList class MainActivity : AppCompatActivity() { private var recyclerView: RecyclerView? = null private var customAdapter: CustomAdapter? = null private val vehicleTypes = arrayOf("Cars", "Bikes", "Air Crafts", "Old Vehicles") private var listItemArrayList: ArrayList<ListItem>? = null private val childnames = arrayOf( "Range Rover", "Lamborghini", "Rolls Royce", "Ferrari", "Harley davidson", "Ducati", "BMW", "Honda", "Boeing", "Airbus", "Royal Air", "Space X", "Horse", "Elephant", "Camel", "Donkey" ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) recyclerView = findViewById<RecyclerView>(R.id.recycler) listItemArrayList = ArrayList() populateList() customAdapter = CustomAdapter(this, listItemArrayList!!) recyclerView!!.adapter = customAdapter recyclerView!!.layoutManager = LinearLayoutManager(applicationContext, LinearLayoutManager.VERTICAL, false) } interface ListItem { fun isHeader():Boolean fun getName():String } private fun populateList() { var headerdone = 0 var childdone = 0 for (i in 0..19) { if (i == 0 || (i == 5) or (i == 10) or (i == 15)) { val vehicleModel = HeaderModel() vehicleModel.setheader(vehicleTypes[headerdone]) listItemArrayList!!.add(vehicleModel) headerdone = headerdone + 1 } else { val childModel = ChildModel() childModel.setChild(childnames[childdone]) listItemArrayList!!.add(childModel) childdone = childdone + 1 } } } }
Some Details
First of all, see the below code lines
private var recyclerView: RecyclerView? = null private var customAdapter: CustomAdapter? = null private val vehicleTypes = arrayOf("Cars", "Bikes", "Air Crafts", "Old Vehicles") private var listItemArrayList: ArrayList<ListItem>? = null
First line is the object of Recyclerview class and second is for object of CustomAdapter class.
Third line is the string array which holds the names of the vehicle types.
Last line is the arraylist with the objects of the interface named ListItem
Now check the following lines of code
private val childnames = arrayOf( "Range Rover", "Lamborghini", "Rolls Royce", "Ferrari", "Harley davidson", "Ducati", "BMW", "Honda", "Boeing", "Airbus", "Royal Air", "Space X", "Horse", "Elephant", "Camel", "Donkey" )
Another string variable with the names of the Vehicle companies. These are the child rows.
Now below is the code for the method populateList() method.
private fun populateList() { var headerdone = 0 var childdone = 0 for (i in 0..19) { if (i == 0 || (i == 5) or (i == 10) or (i == 15)) { val vehicleModel = HeaderModel() vehicleModel.setheader(vehicleTypes[headerdone]) listItemArrayList!!.add(vehicleModel) headerdone = headerdone + 1 } else { val childModel = ChildModel() childModel.setChild(childnames[childdone]) listItemArrayList!!.add(childModel) childdone = childdone + 1 } } }
This method will create data source of the example. An arraylist (listItemArrayList) with object of the interface ListItem is the main datasource.
This method will create the data source listItemArrayList by sorting them by header and child rows.
Now see the below code structure
customAdapter = CustomAdapter(this, listItemArrayList!!) recyclerView!!.adapter = customAdapter recyclerView!!.layoutManager = LinearLayoutManager(applicationContext, LinearLayoutManager.VERTICAL, false)
Compiler will initialize the object of the CustomAdapter class and it will pass listItemArrayList in it’s second parameter.
Adapter will use this listItemArrayList to create child and header rows of the Recycler view.
Finally, compiler will bind the object of the CustomAdapter class to the Recycler view.