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.
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="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.
- The
int type
holds the view type constant. - The
String text
contains the String that’ll be displayed in the TextView. - 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="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.
Source:
https://www.digitalocean.com/community/tutorials/android-recyclerview-example