RecyclerView With Multiple View Types

Today we are going to discuss that how to display RecyclerView With Multiple View Types. Let’s take an example of any scrollable list where we want to display some items with video content and some with image content. Or another example like a we want to display static header and footer in scrollable list. In both of the example we can integrate RecyclerView with multiple viewtypes.

To implement RecyclerView with multiple viewtypes (heterogeneous layouts), most of the work is done within the RecyclerView.Adapter. In particular, there are special methods to be overridden within the adapter:

  • getViewType()
  • onCreateViewHolder()
  • onBindViewHolder()

Before i start with example i have consider several points

  • I should be able to reuse cell in another RecyclerView.
  • I should able to integrate another cell types into RecyclerView.

 

Implementation


Step 1:

Add following dependencies into build.gradle file.

dependencies {   
    implementation 'com.android.support:recyclerview-v7:28.0.0-rc01'
    implementation 'com.github.bumptech.glide:glide:4.7.1'
}

 

Step 2:

Now create different ViewHolder class for Header, Footer and for display MovieList. Extend those classes with RecyclerView.ViewHolder .

HeaderViewHolder.kt

package com.android4dev.recyclerview.adapter

import android.support.v7.widget.RecyclerView
import android.view.View
import kotlinx.android.synthetic.main.list_item_recycler_header.view.*

class HeaderViewHolder(itemView:View):RecyclerView.ViewHolder(itemView) {
    fun bindView(){
        itemView.textHeader.text="Header Section"
    }
}

 

FooterViewHolder.kt

package com.android4dev.recyclerview.adapter

import android.support.v7.widget.RecyclerView
import android.view.View
import kotlinx.android.synthetic.main.list_item_recycler_header.view.*

class FooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bindView() {
        itemView.textHeader.textHeader.text = "Footer Section"
    }
}

 

MovieListViewHolder.kt

package com.android4dev.recyclerview.adapter

import android.support.v7.widget.RecyclerView
import android.view.View
import com.android4dev.recyclerview.model.MovieModel
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.list_item_movie.view.*
import java.util.*

class MovieListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bindView(movieModel: MovieModel) {
        itemView.textMovieTitle.text = movieModel.movieTitle
        itemView.textMovieViews.text = "Views: " + movieModel.movieViews
        itemView.textReleaseDate.text = "Release Date: " + movieModel.releaseDate

        Glide.with(itemView.context).load(movieModel.moviePicture!!).into(itemView.imageMovie)
    }

}

 

Step 3:

Make a layout files for HeaderViewHolder, FooterViewHolder and MovieListViewHolder.

list_item_recycler_header.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/textHeader"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:fontFamily="sans-serif-condensed"
        android:gravity="center"
        android:padding="16dp"
        android:text="Header Section"
        android:textColor="@android:color/white"
        android:textSize="18sp"
        android:textStyle="bold" />


</android.support.constraint.ConstraintLayout>

 

list_item_movie.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="wrap_content"
    android:background="@android:color/white">

    <ImageView
        android:id="@+id/imageMovie"
        android:layout_width="110dp"
        android:layout_height="150dp"
        android:contentDescription="@string/app_name"
        android:scaleType="fitXY"
        android:src="@drawable/ic_avengers" />

    <TextView
        android:id="@+id/textMovieTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingEnd="16dp"
        android:paddingLeft="16dp"
        android:paddingStart="16dp"
        android:textColor="@android:color/black"
        android:textSize="20sp"
        app:layout_constraintStart_toEndOf="@id/imageMovie"
        tools:text="Avengers" />

    <TextView
        android:id="@+id/textMovieViews"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:paddingEnd="16dp"
        android:paddingLeft="16dp"
        android:paddingStart="16dp"
        android:textColor="@android:color/black"
        android:textSize="14sp"
        app:layout_constraintStart_toEndOf="@id/imageMovie"
        app:layout_constraintTop_toBottomOf="@id/textMovieTitle"
        tools:text="Views: 100" />

    <TextView
        android:id="@+id/textReleaseDate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:paddingEnd="16dp"
        android:paddingLeft="16dp"
        android:paddingStart="16dp"
        android:textColor="@android:color/black"
        android:textSize="14sp"
        app:layout_constraintStart_toEndOf="@id/imageMovie"
        app:layout_constraintTop_toBottomOf="@id/textMovieViews"
        tools:text="Release Date: 16 Feb 2018" />

</android.support.constraint.ConstraintLayout>

 

Step 4:

Now create one Adapter class called MovieListHeaderAdapter and extend it using RecyclerView.Adapter.

package com.android4dev.recyclerview.adapter

import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import com.android4dev.recyclerview.R
import com.android4dev.recyclerview.model.MovieModel

class MovieListHeaderAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private var listOfMovies = listOf<MovieModel>()
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            CellType.HEADER.ordinal -> HeaderViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item_recycler_header, parent, false))
            CellType.CONTENT.ordinal -> MovieListViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item_movie, parent, false))
            CellType.FOOTER.ordinal -> FooterViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item_recycler_header, parent, false))
            else -> MovieListViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item_movie, parent, false))
        }
    }


    override fun getItemCount(): Int = listOfMovies.size + 2
    //add 2 size in itemcount because we are going to add header and footer cell in list

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (getItemViewType(position)) {
            CellType.HEADER.ordinal -> {
                val headerViewHolder = holder as HeaderViewHolder
                headerViewHolder.bindView()
            }
            CellType.CONTENT.ordinal -> {
                val headerViewHolder = holder as MovieListViewHolder
                headerViewHolder.bindView(listOfMovies[position - 1])
            }
            CellType.FOOTER.ordinal -> {
                val footerViewHolder = holder as FooterViewHolder
                footerViewHolder.bindView()
            }
        }
    }


    /***
     * This method will return cell type base on position
     */
    override fun getItemViewType(position: Int): Int {
        return when (position) {
            0 -> CellType.HEADER.ordinal
            listOfMovies.size + 1 -> CellType.FOOTER.ordinal
            else -> CellType.CONTENT.ordinal
        }
    }

    /***
     * Enum class for recyclerview Cell type
     */
    enum class CellType(viewType: Int) {
        HEADER(0),
        FOOTER(1),
        CONTENT(2)
    }

    fun setMovieList(listOfMovies: List<MovieModel>) {
        this.listOfMovies = listOfMovies
        notifyDataSetChanged()
    }
}

 

In above Adapter class we have one enum class for different viewtypes.


enum class CellType(viewType: Int) {
HEADER(0),
FOOTER(1),
CONTENT(2)
}

getItemViewType() will return celltype according to position. In above Adapter class we will return HEADER view type for adapter’s 0 position, FOOTER view type for listOfMovies.size + 1 adapter’s position, CONTENT view type for all other remaining positions.

In onCreateViewHolder() class we will inflate different layouts depends upon getItemViewType() value. 

In a same way we will call different ViewHolder classes depends upon getItemViewType(position) value.

Step 5:

Make Activity class called RecyclerViewHeaderFooterActivity and add following code into it.

package com.android4dev.recyclerview

import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import com.android4dev.recyclerview.adapter.MovieListAdapter
import com.android4dev.recyclerview.adapter.MovieListHeaderAdapter
import com.android4dev.recyclerview.base.BaseActivity
import com.android4dev.recyclerview.model.MovieModel
import com.android4dev.recyclerview.recyclerview_helper.DividerItemDecoration
import com.android4dev.recyclerview.recyclerview_helper.VerticalSpaceItemDecoration
import kotlinx.android.synthetic.main.activity_main.*

class RecyclerViewHeaderFooterActivity : BaseActivity() {

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

    private fun initView() {
        recyclerViewMovies.layoutManager = LinearLayoutManager(this)
        recyclerViewMovies.addItemDecoration(VerticalSpaceItemDecoration(48))

        //This will for default android divider
        recyclerViewMovies.addItemDecoration(DividerItemDecoration(this))

        //This will for custom divider
//        recyclerViewMovies.addItemDecoration(DividerItemDecoration(this, R.drawable.drawable_divider_view))
        val movieListAdapter = MovieListHeaderAdapter()
        recyclerViewMovies.adapter = movieListAdapter
        movieListAdapter.setMovieList(generateDummyData())
    }

    private fun generateDummyData(): List<MovieModel> {
        val listOfMovie = mutableListOf<MovieModel>()

        var movieModel = MovieModel(1, "Avengers", 500, "16 Feb 2018", R.drawable.ic_avengers)
        listOfMovie.add(movieModel)

        movieModel = MovieModel(2, "Avengers: Age of Ultron", 400, "17 March 2018", R.drawable.ic_avengers_2)
        listOfMovie.add(movieModel)

        movieModel = MovieModel(3, "Iron Man 3", 550, "17 April 2018", R.drawable.ic_ironman_3)
        listOfMovie.add(movieModel)

        movieModel = MovieModel(4, "Avengers: Infinity War", 1500, "15 Jan 2018", R.drawable.ic_avenger_five)
        listOfMovie.add(movieModel)

        movieModel = MovieModel(5, "Thor: Ragnarok", 200, "17 March 2018", R.drawable.ic_thor)
        listOfMovie.add(movieModel)

        movieModel = MovieModel(6, "Black Panther", 250, "17 May 2018", R.drawable.ic_panther)
        listOfMovie.add(movieModel)

        movieModel = MovieModel(7, "Logan", 320, "17 Dec 2018", R.drawable.ic_logan)
        listOfMovie.add(movieModel)

        return listOfMovie
    }
}

 

Step 6:

Create layout file for Activity class.

 

<?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"
    android:background="@android:color/white"
    tools:context=".RecyclerViewLinearLayoutActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerViewMovies"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:overScrollMode="never"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

 

Step 7:

Now create two more classes for ItemDecoration.

VerticalSpaceItemDecoration.kt

package com.android4dev.recyclerview.recyclerview_helper

import android.graphics.Rect
import android.support.v7.widget.RecyclerView
import android.view.View

/***
 * Made by Lokesh Desai (Android4Dev)
 */
class VerticalSpaceItemDecoration(private val verticalSpaceHeight: Int) : RecyclerView.ItemDecoration() {

    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView,
                                state: RecyclerView.State) {
        outRect.bottom = verticalSpaceHeight
    }
}

 

DividerItemDecoration.kt

package com.android4dev.recyclerview.recyclerview_helper

import android.content.Context
import android.support.v7.widget.RecyclerView
import android.support.v4.content.ContextCompat
import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.drawable.Drawable

/***
 * Made by Lokesh Desai (Android4Dev)
 */
class DividerItemDecoration
/**
 * Default divider will be used
 */(context: Context) : RecyclerView.ItemDecoration() {
    private val ATTRS = intArrayOf(android.R.attr.listDivider)

    private var divider: Drawable? = null

    init {
        val styledAttributes = context.obtainStyledAttributes(ATTRS)
        divider = styledAttributes.getDrawable(0)
        styledAttributes.recycle()
    }

    /**
     * Custom divider will be used
     */
    constructor(context: Context, resId: Int) : this(context) {
        divider = ContextCompat.getDrawable(context, resId)
    }

    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
        val left = parent.paddingLeft
        val right = parent.width - parent.paddingRight

        val childCount = parent.childCount
        for (i in 0 until childCount) {
            val child = parent.getChildAt(i)

            val params = child.layoutParams as RecyclerView.LayoutParams

            val top = child.bottom + params.bottomMargin
            val bottom = top + divider!!.intrinsicHeight

            divider!!.setBounds(left, top, right, bottom)
            divider!!.draw(c)
        }
    }
}

 

Above both classes will help us to add Dividers and Proper Spacing between all the Grid Items. 

Now Run the project and wait for the result.

Conclusion

With small amount of effort we can successfully integrate RecyclerView with Multiple View Types. All we have to do is create different ViewHolders classes for different CellType.

Source Code

Check out Github Project Which Contains All RecyclerView Examples

You Can Also Refer

 

https://android4dev.com

Hi, I am Android Developer and Founder of Android4Dev. Right now my interest is in RxKotlin, MVVM and Dagger2. I am Kotlin Lover. Kotlin made me more productive in terms of coding. Do you want me to code for you ? Please don’t hesitate to contact me.