AndroidPub
Published in

AndroidPub

Easily Adding Nested Recycler View in Android

Recycler View is one of the most used view components in android. Also, the design of recycler views is getting complex day by day such as Recycler View with multiple view types, nested recycler view, nested recycler view with multiple view types. Today, we are going to talk about Nested Recycler View with one view type.

What will it look like?

Nested Recycler View would look like something shown below. A vertical infinite scrollable list with horizontal infinite carousels.

Key Idea

Before we begin with the code, let’s understand the basic key idea involved in designing nested recycler view. So, the key idea is that on the top level we will have a list of card view objects which we will show in recycler view. So at the top level, we will have single recycler view of card views. Now each card view, in turn, will have a recycler view as it’s one of the children and this child recycler view will act as a horizontal carousel.

Github Link

If you directly want to jump straight to the full source code then visit here.
Otherwise, follow through the whole blog for a detailed step by step implementation of nested recycler view in Kotlin.

Cool, Show me the Code!

Okay, let’s directly jump to code now. Open a new project in Android Studio with default settings and Kotlin support checked in.

Adding Dependencies
Modify your build.gradle file for app module to add dependencies for recycler-view and card view as shown below:

//build.gradledependecies{
...
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.android.support:cardview-v7:27.1.1'
...
}

Now create three packages in the main directory:

  1. Models — We will keep all our models, data factory and objects here.
  2. Views — We will keep all our views, activities and ui related stuff here
  3. ViewModel — ViewModels are like a bridge between models and views.

Let’s prepare UI for our App

A nested recycler view example

This will be the final result of our this short activity. Our final view/UI will look like this with an infinite scrollable list of card views with each card view holding another child infinite horizontal carousel.

So let’s follow a top-down approach and start with the activity_main.xml file. Modify it to have a recycler view as shown:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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=".views.MainActivity">

<android.support.v7.widget.RecyclerView
android:id="@+id/rv_parent"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</android.support.constraint.ConstraintLayout>

Now let’s prepare our card view layout which will be a single item in the above-created recycler view as shown below:

parent_recycler.xml

<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="2dp"
card_view:cardBackgroundColor="#fff"
card_view:cardCornerRadius="5dp"

card_view:cardElevation="4dp"
card_view:cardUseCompatPadding="true">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:id="@+id/textView"
style="@style/Base.TextAppearance.AppCompat.Subhead"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignStart="@+id/rv_child"
android:padding="20dp"
android:text="Hello World"
android:textColor="@color/colorAccent"
android:textStyle="bold" />

<android.support.v7.widget.RecyclerView
android:id="@+id/rv_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:padding="20dp"
android:layout_marginTop="30dp"
android:orientation="horizontal"
tools:layout_editor_absoluteX="74dp" />

</RelativeLayout>

</android.support.v7.widget.CardView>

Now let’s create View for the recycler view present in card view. As shown in the above figure, the card view contains a list of image view with text view and thus that is going to be our layout structure as shown below. Let’s call this child_recycler.xml.

child_recycler.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<TextView
android:id="@+id/child_textView"
android:layout_width="129dp"
android:layout_height="37dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="1dp"
android:background="@android:color/darker_gray"
android:padding="10dp"
android:text="TextView"
android:textColor="@android:color/white"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/child_imageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />

<ImageView
android:id="@+id/child_imageView"
android:layout_width="126dp"
android:layout_height="189dp"
android:layout_marginBottom="38dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="42dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
app:srcCompat="@drawable/aviator" />

</android.support.constraint.ConstraintLayout>

As clear from the layout file, this view is Constraint layout with text view for title and image view for the image.

Now Let’s create the Models

We will have two models:
1. ParentModel — This will store data for card view or card object.
2. ChildModel — This will store data for Item for recycler-view present in card.

ParentModel.kt
This is a Kotlin data class that stores data for a single card view and so contains one text view for the title of the card and List of ChildModel to show in the recycler view present in card view.

data class ParentModel (
val title : String = "",
val children : List<ChildModel>
)

ChildModel.kt
This is also a Kotlin data class that stores data for items present in the recycler view of the card view. That’s why it contains one text view for title of the movie and one image view for the image.

data class ChildModel(
val image : Int = -1,
val title : String = ""
)

Now Ideally, the data should come from some database either remote or local but for keeping the simplicity of our blog, I have created ParentDataFactory and ChildDataFactory class to get respective data.

ParentDataFactory.kt

object ParentDataFactory{
private val random = Random()

private val titles = arrayListOf( "Now Playing", "Classic", "Comedy", "Thriller", "Action", "Horror", "TV Series")

private fun randomTitle() : String{
val index = random.nextInt(titles.size)
return titles[index]
}

private fun randomChildren() : List<ChildModel>{
return ChildDataFactory.getChildren(20)
}

fun getParents(count : Int) : List<ParentModel>{
val parents = mutableListOf<ParentModel>()
repeat(count){
val parent = ParentModel(randomTitle(), randomChildren())
parents.add(parent)
}
return parents
}
}

As shown above, in the ParentDataFactory object (Equivalent to Singleton class in Java), it has two private functions to generate random title and random image and one public function for getting a list of parent models with a passed size.
Similarly, we will also create the ChildDataFactory as shown below:

ChildDataFactory.kt

object ChildDataFactory{

private val random = Random()

private val titles = arrayListOf( "Aviator", "Now you can See me", "God Father", "Mission Impossible", "3 idiots")

private fun randomTitle() : String{
val index = random.nextInt(titles.size)
return titles[index]
}

private fun randomImage() : Int{
return R.drawable.aviator
}

fun getChildren(count : Int) : List<ChildModel>{
val children = mutableListOf<ChildModel>()
repeat(count){
val child = ChildModel(randomImage(), randomTitle())
children.add(child)
}
return children
}


}

Let’s add View and Recycler view Adapters

Now let’s complete our view and for that, we will first create two RecyclerView Adapters for two recycler views. We will start with ChildAdapter which is just like any other RecyclerView adapter as shown below:

ChildAdapter.kt

class ChildAdapter(private val children : List<ChildModel>)
: RecyclerView.Adapter<ChildAdapter.ViewHolder>(){

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

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

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

override fun onBindViewHolder(holder: ViewHolder,
position: Int) {
val child = children[position]
holder.imageView.setImageResource(child.image)
holder.textView.text = child.title
}


inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){

val textView : TextView = itemView.child_textView
val imageView: ImageView = itemView.child_imageView

}
}

As you can see here, we are passing the data to the Adapter. We can also delegate this to some view model but again is left out to have our focus on creating nested recycler view. Also, the view in ViewHolder is directly accessed without view binding as it is default in Kotlin.

Now let’s add ParentAdapter which will have text view and a recycler view and thus in this adapter in its onBindViewHolder method we will be setting up the values for recycler view as shown below:

ParentAdapter.kt

class ParentAdapter(private val parents : List<ParentModel>) :    RecyclerView.Adapter<ParentAdapter.ViewHolder>(){private val viewPool = RecyclerView.RecycledViewPool()override fun onCreateViewHolder(parent: ViewGroup, 
viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.parent_recycler,parent,false)
return ViewHolder(v)
}

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

override fun onBindViewHolder(holder: ViewHolder,
position: Int) {
val parent = parents[position]
holder.textView.text = parent.title
val childLayoutManager = LinearLayoutManager( holder.recyclerView.context, LinearLayout.HORIZONTAL, false)
childLayoutManager.initialPrefetchItemCount = 4holder.recyclerView.apply {
layoutManager = childLayoutManager
adapter = ChildAdapter(parent.children)
setRecycledViewPool(viewPool)
}

}


inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){
val recyclerView : RecyclerView = itemView.rv_child
val textView:TextView = itemView.textView
}
}

As seen above in onBindViewHolder, we are setting up our child recycler view by passing layout manager, adapter by creating a new ChildAdapter and also you can see that we are setting the recycledViewPool for each child recycler view to the same view pool that is created earlier. Also, the initial prefetch amount for child recycler view is set to 4 for our use case. These are done to optimize the nested recycler view. You can read more about it here.

Now let’s modify our MainActivity to have the final setup ready. Modify the MainActivity.kt as follows:

class MainActivity : AppCompatActivity() {

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

initRecycler()
}

private fun initRecycler(){
recyclerView = rv_parent

recyclerView.apply {
layoutManager = LinearLayoutManager(this@MainActivity,
LinearLayout.VERTICAL, false)
adapter = ParentAdapter(ParentDataFactory
.getParents(40))
}

}
}

All is Well, Hit Run!

Now everything seems good, hit run and see your beautiful final result. So you can see how easy it is to add nested recycler views and also using Kotlin for development make development much more fun and easy.

Happy Coding!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store