Android RecyclerView Example – Multiple ViewTypes

Filed Under: Android

Up until now we’ve displayed same type of Views within a RecyclerView. In this tutorial, we’ll implement heterogeneous layouts inside a RecyclerView.

RecyclerView

RecyclerView with heterogeneous layouts is commonly used in to display section headers and details(Both require different layouts, hence different view type). Also, it’s used in a Newsfeed Application(like Facebook, Instagram) that display essentially different views for different types. Example: text, image, gif, video etc. Each of these requires a different layout type inside the RecyclerView.

It’s also used in a NavigationDrawer to separate the Header from the rest of the section.
Without wasting any time, let’s implement it in our application.

Android RecyclerView Multiple ViewType Project Structure

We’ll be implementing three view types (text, image, audio) that are inflated by three different layouts. Each has its own implementation specified in the adapter class.

android recycler view multiple view type project

Code

Our activity_main.xml contains the CoordinatorLayout as the root and the RecyclerView acts as it’s child view.


<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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:fitsSystemWindows="true"
    tools:context="com.journaldev.recyclerviewmultipleviewtype.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:layout_height="match_parent" />


</android.support.design.widget.CoordinatorLayout>

Take note of the line app:layout_behavior="@string/appbar_scrolling_view_behavior" inside RecyclerView. Removing this would scroll the RecyclerView over the whole screen thereby overlapping it with the AppBarLayout.

The Model.java class that populates the data in the Adapter is given below


public class Model {

    public static final int TEXT_TYPE=0;
    public static final int IMAGE_TYPE=1;
    public static final int AUDIO_TYPE=2;

    public int type;
    public int data;
    public String text;

    public Model(int type, String text, int data)
    {
        this.type=type;
        this.data=data;
        this.text=text;
    }
}

It consists of three data types.

  1. The int type holds the view type constant.
  2. The String text contains the String that’ll be displayed in the TextView.
  3. The int data variable is used to store the respective data that we’ll be populating. Ideally it’ll contain a drawable or raw type resource.

The MainActivity.java class is given below


package com.journaldev.recyclerviewmultipleviewtype;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        
        ArrayList<Model> list= new ArrayList();
        list.add(new Model(Model.TEXT_TYPE,"Hello. This is the Text-only View Type. Nice to meet you",0));
        list.add(new Model(Model.IMAGE_TYPE,"Hi. I display a cool image too besides the omnipresent TextView.",R.drawable.wtc));
        list.add(new Model(Model.AUDIO_TYPE,"Hey. Pressing the FAB button will playback an audio file on loop.",R.raw.sound));
        list.add(new Model(Model.IMAGE_TYPE,"Hi again. Another cool image here. Which one is better?",R.drawable.snow));

        MultiViewTypeAdapter adapter = new MultiViewTypeAdapter(list,this);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, OrientationHelper.VERTICAL, false);

        RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        mRecyclerView.setLayoutManager(linearLayoutManager);
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mRecyclerView.setAdapter(adapter);
    }
}

The R.raw.sound is a sound.mp3 file that’ll be played in the Audio View Type. The Adapter class for the RecyclerView contains three major methods that need to be overridden.

  • getItemViewType()
  • onCreateViewHolder()
  • onBindViewHolder()

We’ll be using switch statements in the getItemViewType() method to return the respective viewType. This viewType variable is internal to the Adapter class. It’s used in the onCreateViewHolder() and onBindViewHolder to inflate and populate the mapped layouts.

Before we jump into the implementation of the Adapter class, let’s look at the types of layouts that are defined for each view type.

text_type.xml


<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/card_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="@dimen/activity_horizontal_margin"
    card_view:cardElevation="10dp">
    <TextView
        android:id="@+id/type"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        />


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

image_type.xml


<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/card_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="@dimen/activity_horizontal_margin"
    card_view:cardElevation="10dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/type"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            />

        <ImageView
            android:id="@+id/background"
            android:layout_width="match_parent"
            android:layout_height="150dp"
            android:scaleType="centerCrop"
            android:src="@drawable/snow"
            />

    </LinearLayout>

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

audio_type.xml


<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/card_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="@dimen/activity_horizontal_margin"
    card_view:cardElevation="10dp">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        <TextView
            android:id="@+id/type"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            />

        <android.support.design.widget.FloatingActionButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:tint="#FFFFFF"
            android:id="@+id/fab"
            android:layout_below="@+id/type"
            android:layout_margin="@dimen/activity_horizontal_margin"
            android:src="@drawable/volume"/>

        </RelativeLayout>

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

Note: Add the following dependency for CardView in the build.gradle file


compile 'com.android.support:cardview-v7:24.2.0'

Make sure that the version number of the appcompat dependency matches with the cardview one. (It’s 24.2.0 for me presently. Can be different for you.)

We’ll be creating three separate ViewHolder classes for each of the above layouts as shown in the MultiViewTypeAdapter.java class below.


package com.journaldev.recyclerviewmultipleviewtype;

import android.content.Context;
import android.media.MediaPlayer;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;

/**
 * Created by anupamchugh on 09/02/16.
 */
public class MultiViewTypeAdapter extends RecyclerView.Adapter {

    private ArrayList<Model>dataSet;
    Context mContext;
    int total_types;
    MediaPlayer mPlayer;
    private boolean fabStateVolume = false;

    public static class TextTypeViewHolder extends RecyclerView.ViewHolder {

        TextView txtType;
        CardView cardView;

        public TextTypeViewHolder(View itemView) {
            super(itemView);

            this.txtType = (TextView) itemView.findViewById(R.id.type);
            this.cardView = (CardView) itemView.findViewById(R.id.card_view);
        }
    }

    public static class ImageTypeViewHolder extends RecyclerView.ViewHolder {

        TextView txtType;
        ImageView image;

        public ImageTypeViewHolder(View itemView) {
            super(itemView);

            this.txtType = (TextView) itemView.findViewById(R.id.type);
            this.image = (ImageView) itemView.findViewById(R.id.background);
        }
    }

    public static class AudioTypeViewHolder extends RecyclerView.ViewHolder {

        TextView txtType;
        FloatingActionButton fab;

        public AudioTypeViewHolder(View itemView) {
            super(itemView);

            this.txtType = (TextView) itemView.findViewById(R.id.type);
            this.fab = (FloatingActionButton) itemView.findViewById(R.id.fab);
        }
    }

    public MultiViewTypeAdapter(ArrayList<Model>data, Context context) {
        this.dataSet = data;
        this.mContext = context;
        total_types = dataSet.size();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view;
        switch (viewType) {
            case Model.TEXT_TYPE:
                view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_type, parent, false);
                return new TextTypeViewHolder(view);
            case Model.IMAGE_TYPE:
                view = LayoutInflater.from(parent.getContext()).inflate(R.layout.image_type, parent, false);
                return new ImageTypeViewHolder(view);
            case Model.AUDIO_TYPE:
                view = LayoutInflater.from(parent.getContext()).inflate(R.layout.audio_type, parent, false);
                return new AudioTypeViewHolder(view);
        }
        return null;
    }

    @Override
    public int getItemViewType(int position) {

        switch (dataSet.get(position).type) {
            case 0:
                return Model.TEXT_TYPE;
            case 1:
                return Model.IMAGE_TYPE;
            case 2:
                return Model.AUDIO_TYPE;
            default:
                return -1;
        }
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int listPosition) {

        Model object = dataSet.get(listPosition);
        if (object != null) {
            switch (object.type) {
                case Model.TEXT_TYPE:
                    ((TextTypeViewHolder) holder).txtType.setText(object.text);

                    break;
                case Model.IMAGE_TYPE:
                    ((ImageTypeViewHolder) holder).txtType.setText(object.text);
                    ((ImageTypeViewHolder) holder).image.setImageResource(object.data);
                    break;
                case Model.AUDIO_TYPE:

                    ((AudioTypeViewHolder) holder).txtType.setText(object.text);

                    ((AudioTypeViewHolder) holder).fab.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {

                            if (fabStateVolume) {
                                if (mPlayer.isPlaying()) {
                                    mPlayer.stop();

                                }
                                ((AudioTypeViewHolder) holder).fab.setImageResource(R.drawable.volume);
                                fabStateVolume = false;

                            } else {
                                mPlayer = MediaPlayer.create(mContext, R.raw.sound);
                                mPlayer.setLooping(true);
                                mPlayer.start();
                                ((AudioTypeViewHolder) holder).fab.setImageResource(R.drawable.mute);
                                fabStateVolume = true;

                            }
                        }
                    });
                    break;
            }
        }
    }

    @Override
    public int getItemCount() {
        return dataSet.size();
    }
}

In the above code, we’re keeping a global boolean variable for storing the volume button state that’s toggled at each click(along with changing the image resource for the FloatingActionButton).

The output of the above application is given below.
android recyclerview multiple type output

This brings an end to this tutorial. You can download the final Android RecyclerViewMultipleViewType Project from the below link.

Comments

  1. nagendran says:

    Hi,

    How to load the data insteads of list.add(new Model(Model.TEXT_TYPE,”Hello. This is the Text-only View Type. Nice to meet you”,0));

  2. vvv says:

    Thank you! But I recommend to use any library, ex https://github.com/vivchar/RendererRecyclerViewAdapter

  3. Viraj Pawar says:

    I want to add multiple items of same date in single CARDVIEW but instead it is creating multiple card view for the items of same date…can someone please help me out regarding this problem

  4. sattar_sh says:

    very very good tutorial .tnx
    how can set header for any of data model type in recycler
    pls help me…

  5. Thanosthepurpleone says:

    thank you. It works great

  6. PRAVAS says:

    I need Video to play automatically in Recyclerview/scroll view, if view contains video. This is very much similar with Facebook. If user scrolls down/up and visible area contains video that system will play video and if still scroll then it automatically stops that video. It should work like , suppose if first visible item visibility from top is < 50% then first visible item should play other wise second visible item should play. Thanks!

  7. ASHWINI VASUDEVA says:

    Hi, can you please let me know where to add the onclicklistner in tis code for Image_type ? Thanks you

  8. ASHWINI VASUDEVA says:

    Hello, this was really a very good code and more clear in terms of understanding. Can you please help me one more thing, i want to add onclicklistner for image type. where should i add and what will be code for that? I’m new to android and still in learning process. Thanks a lot for the code 🙂

  9. Pranay Rangne says:

    Nice Tutorial, but my case is a little bit different I have a JSON Array
    {
    “imager”: [{
    “title”: “Guru”,
    “images”: [“images\/6.png”, “images\/androidIntro.png”, “images\/barretr_Earth.png”]
    }]
    }
    like this now i want to fetch data from this array i have done RecyclerViewAdapater part
    but i want to show Title only One Time and Images all
    I thought of using two Lists in recyclerview but it’s giving me an error

    Process: com.webtreater.pranay.jsondata, PID: 9513
    java.lang.IndexOutOfBoundsException: Index: 7, Size: 7
    at java.util.ArrayList.get(ArrayList.java:437)
    at com.webtreater.pranay.jsondata.RecyclerViewAdapater.onBindViewHolder(RecyclerViewAdapater.java:45)
    at com.webtreater.pranay.jsondata.RecyclerViewAdapater.onBindViewHolder(RecyclerViewAdapater.java:20)

    like this
    can you help me with this please?

    1. Abdoul says:

      Hi, am having the same error?? java.lang.IndexOutOfBoundsException: Index: 4, Size: 4 ie:
      the issue is that i can update the first 4 rows very well, but after that it crashes. and the 4 different viewHolders are working very well but onBindViewHolder updating the remaining lists it crashes!!
      {
      “data1”: [],
      “data2”: [],
      “data3”: [],
      “data4”: []
      }

  10. Simon Eric says:

    Great Tutorial… Haven’t tried it but it has opened my Mind.

  11. Abdullah says:

    Hi,
    How do you set a edit text viewholder in bindview method??

  12. Siphamandla Gcabashe says:

    Hello I followed your solution but i get stuck some how can you please check for me here

    https://stackoverflow.com/questions/50903292/inter-changing-layoutinflator-view-using-if-statements

  13. Anny says:

    Hi ,Can add Multiple view with with multiple rows in One Adapter??

    Like CustomerView=>
    Item1
    item2
    item3

    DoctorView=>
    Item1
    item2
    item3

    MedicineView=>
    Item1
    item2
    item3

    Any Solutions? I don’t want make multiple adapters.

    view3=> Item1
    item2
    item3

  14. Sridhar.GN says:

    Hi Anupam Chugh, Your project is quiet interesting. I have add one more audio in the arraylist. Start and stop audio in single track is working fine. But when i tried to start the second audio while first audio track is playing, then the first track gets stopped but the second track cannot play. Even the floating button also never change its characteristics respectively in the listitem. Kindly help me to do resolve this, which is very helpful to me. Thanks.

  15. caviru says:

    Hello how can i implement video instead of audio ?? or it will be work if i change extension of the audio to video format. will it work ??
    sorry i am a beginner in android development.

    And thank you for the amazing tutorial

    1. Anupam says:

      Hi Caviru,
      You need to use a VideoView widget to play videos.

      Thanks

  16. Nihar says:

    I want to scroll the images in the image type xml files. Will it be possible.?

  17. laura says:

    Hi Anupam,
    I’m getting an error in MultiViewTypeAdapter “cannot resolve symbol ‘type’ in the getItemViewType method…
    any ideas?
    @Override
    public int getItemViewType(int position) {

    switch (dataSet.get(position).type) {
    case 0:
    return Model.TEXT_TYPE;
    case 1:
    return Model.IMAGE_TYPE;
    case 2:
    return Model.AUDIO_TYPE;
    default:
    return -1;
    }
    }

    Thank you,
    Laura

    1. Anupam says:

      Hi Laura,
      Ensure that the variable “type” is a public variable in the Model.java class. Try clean build/rebuild the project. It should work fine.

      Thanks

  18. Puneethsai says:

    Can we Load data for into cards from Json also and how to add it..

    1. Anupam says:

      Yes, you can load data using JSONArray into the Cards.

  19. barani says:

    Thanks a lot!!!

    1. Anupam says:

      Glad that it was helpful to you Barani.

  20. haris says:

    change private ArrayList dataSet;
    to
    ArrayList dataSet

  21. Ahmed Osman says:

    Hello,

    I have a problem with *dataSet.get(position).type* what does this “type” refer to ?…. in the method:

    @Override
    public int getItemViewType(int position) {

    switch (dataSet.get(position).type) {
    case 0:
    return Model.TEXT_TYPE;
    case 1:
    return Model.IMAGE_TYPE;
    case 2:
    return Model.AUDIO_TYPE;
    default:
    return -1;
    }
    }

    1. Anupam says:

      type is a variable defined in the class Model.java

  22. Manmohan says:

    Perfect Thank you. Damm good.

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages