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 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.
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.
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="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://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.
int type
holds the view type constant.String text
contains the String that’ll be displayed in the TextView.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="https://schemas.android.com/apk/res-auto"
xmlns:android="https://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="https://schemas.android.com/apk/res-auto"
xmlns:android="https://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="https://schemas.android.com/apk/res-auto"
xmlns:android="https://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. This brings an end to this tutorial. You can download the final Android RecyclerViewMultipleViewType Project from the below link.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
Perfect Thank you. Damm good.
- Manmohan
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; } }
- Ahmed Osman
change private ArrayList dataSet; to ArrayList dataSet
- haris
Thanks a lot!!!
- barani
Can we Load data for into cards from Json also and how to add it…
- Puneethsai
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
- laura
I want to scroll the images in the image type xml files. Will it be possible.?
- Nihar
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
- caviru
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.
- Sridhar.GN
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
- Anny