How to Create Recyclerview With Multiple View Types

How to create RecyclerView with multiple view types

Yes, it's possible. Just implement getItemViewType(), and take care of the viewType parameter in onCreateViewHolder().

So you do something like:

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
class ViewHolder0 extends RecyclerView.ViewHolder {
...
public ViewHolder0(View itemView){
...
}
}

class ViewHolder2 extends RecyclerView.ViewHolder {
...
public ViewHolder2(View itemView){
...
}

@Override
public int getItemViewType(int position) {
// Just as an example, return 0 or 2 depending on position
// Note that unlike in ListView adapters, types don't have to be contiguous
return position % 2 * 2;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new ViewHolder0(...);
case 2: return new ViewHolder2(...);
...
}
}

@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
switch (holder.getItemViewType()) {
case 0:
ViewHolder0 viewHolder0 = (ViewHolder0)holder;
...
break;

case 2:
ViewHolder2 viewHolder2 = (ViewHolder2)holder;
...
break;
}
}
}

RecyclerView with multiple view types with Generics

TLDR;

Well, here's a code which should work:

class HomePageRecylerAdapter(private val data: ArrayList<Any>) : RecyclerView.Adapter<BaseViewHolder<Any>>() {

companion object {
const val typeCarousel = 0
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<Any> {
return when (viewType) {
typeCarousel -> {
val view =
LayoutInflater.from(parent.context).inflate(0, parent, false)
CarouselViewHolder(view) as BaseViewHolder<Any>
}
else -> throw IllegalArgumentException("Invalid view type")
}

}

override fun getItemCount() = data.size

override fun onBindViewHolder(holder: BaseViewHolder<Any>, position: Int) {
val element = data[position]
when (element) {
is HomeCarousel -> {
holder.bind(element)
}
else -> IllegalArgumentException("Invalid binding")
}
}



override fun getItemViewType(position: Int): Int {
val element = data[position]
return when (element) {
is HomeCarousel -> typeCarousel
else -> throw IllegalArgumentException("Invalid type of data {$position}")
}
}
inner class CarouselViewHolder(itemView: View) : BaseViewHolder<HomeCarousel>(itemView) {
override fun bind(item: HomeCarousel) {
}
}
}
abstract class BaseViewHolder<in T: Any>(itemView: View) :
RecyclerView.ViewHolder(itemView) {
abstract fun bind(item: T)
}

Explanation

Possibly this answer may help give you more insights - https://stackoverflow.com/a/51110484/2674983

Although, I find the justification in the answer wrong. The issue is not that the adapter is written in Java, but rather it requires both the subtype of viewholder to be returned and also passes it as argument. Otherwise, it would be easy to use something like use-site variance.

I think the correct way to solve it would've been to use contravariance:

abstract class BaseViewHolder<in T>(itemView: View) :
RecyclerView.ViewHolder(itemView) {
abstract fun bind(item: T)
}

That's because the object of type T can by anything and it needs to pass to a setter, so we only need to set that into the ViewHolder. However, contrapositive has an interesting twist... subtyping works in opposite direction now! So, now BaseViewHolder<HomeCarousel> is no longer a subtype of BaseViewHolder<Any>, since the subtyping is reversed.

To fix it, we could do "unsafe casting", as I've done in the code.

Oh, and using BaseViewHolder<*> (star-projection) does solve subtyping issues, since every instance of BaseViewHolder is now a subtype but it places the restrictions of both covariance and contravariance, so not useful here.

RecyclerView with multiple ViewHolder types

you shouldn't do this that way, this single/first item isn't a collection member. don't add this dummy view, instead inform adapter that there is one extra item

private static final int EXTRA_ITEMS_ON_TOP = 1; // num of buttons

private static final int TYPE_BUTTON = 0;
private static final int TYPE_LIST_ITEM =1;

@Override
public int getItemCount() {
return galleryModelList.size() + EXTRA_ITEMS_ON_TOP;
}

@Override
public int getItemViewType(int position) {
// return TYPE_BUTTON type first position(s)
return position < EXTRA_ITEMS_ON_TOP ? TYPE_BUTTON : TYPE_LIST_ITEM;
}

and inside onBindViewHolder just recalculate position

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder.getItemViewType() == TYPE_BUTTON) {
//... position < EXTRA_ITEMS_ON_TOP, so 0 only in current config
} else {
int realPosition = position - EXTRA_ITEMS_ON_TOP;
GalleryModel model = galleryModelList.get(realPosition);
...
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TYPE_BUTTON)
return new NewImageViewHolder(RowNewImageBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
else
return new GalleryViewHolder(RowGalleryBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
}

your current way is kind of workaround. it may work, but further you may want to obtain full list from adapter and you will get list with one extra item (dummy one) and as it is fulfilled with nulls you may get some NullPointerException. for fixing this you will probably make some if, which complicates code in next place

RecyclerView with multiple view types and data source

You should merge data to one data source only. You can try this way:

  1. Create data source class

    public class Data {
    int type; // 1 is article and 2 is youtubeitem
    public Article article;
    public YouTubeItem youTubeItem;
    }
  2. Now merge two data source to only one

    public List<Data> merge(Articel[] articles, List<YouTubeItem> items) {
    List<Data> datas = new ArrayList<>();
    for(Article article : articles) {
    Data data = new Data();
    data.article = article;
    data.youTubeItem = null;
    data.type = 1;
    datas.add(data);
    }

    for(YouTubeItem item : items) {
    Data data = new Data();
    data.article = null;
    data.youTubeItem = item;
    data.type = 2;
    datas.add(data);
    }

    return datas;
    }
  3. Change constructor of adapter

    private List<Data> datas;

    public SimpleStringRecyclerViewAdapter(Context context, List<Data> datas )
    {
    this.datas = datas;
    }
  4. Change get Item count

    public override int ItemCount
    {

    get
    {
    return datas.Count();
    }
    }
  5. Change getViewType

    public override int GetItemViewType(int position)
    {
    if (datas.get(position).type == 1)
    {
    return Resource.Layout.List_Item;
    }

    else
    {
    return Resource.Layout.VideoList;
    }
    }

EDITED: For merge random method

 public List<Data> mergeRandom(Articel[] articles, List<YouTubeItem> items) {
List<Data> datas = new ArrayList<>();

List<Integer> random = new ArrayList<>();
int maxLength = articles.length + items.size();
for(int i = 0; i< maxLength; i++) {
random.add(i);
}

while (random.size() > 0) {
// get random item
int index = new Random().nextInt(random.size());
int position = random.get(index);

if(position <= article.length - 1) {
Data data = new Data();
data.article = articles[position];
data.youTubeItem = null;
data.type = 1;
datas.add(data);
} else {
Data data = new Data();
data.article = null;
data.youTubeItem = items.get(position - article.length);
data.type = 2;
datas.add(data);
}

random.remove(index);
}

return datas;
}

For merge odd&even method

List<Data> mergeOddEven(Articel[] articles, List<YouTubeItem> items) {
List<Data> datas = new ArrayList<>();

int articleIndex = 0;
int youtubeIndex = 0;
int length = articles.length + items.size();

for(int i = 0; i< length; i++) {
if(articleIndex >= articles.length || youtubeIndex >= items.size()) {
if(articleIndex < articles.length) {
for(int j = articleIndex; j < articles.length ; j++) {
Data data = new Data();
data.article = articles[j];
data.youTubeItem = null;
data.type = 1;
datas.add(data);

}
} else {
for(int j = youtubeIndex; j < items.size() ; j++) {
Data data = new Data();
data.article = null;
data.youTubeItem = items.get(j);
data.type = 2;
datas.add(data);

}
}

break;
}


if(i % 2 == 0) {
Data data = new Data();
data.article = articles[articleIndex];
data.youTubeItem = null;
data.type = 1;
datas.add(data);

articleIndex++;
} else {
Data data = new Data();
data.article = null;
data.youTubeItem = tems.get(youtubeIndex);
data.type = 2;
datas.add(data);

youtubeIndex++;
}
}

return datas;
}

Hope it help

Create a RecyclerView with multiple view from layouts

Here is the answer

Follow this steps

First Create a two layout for your Multiple viewType

SAMPLE CODE

layout.layout_one

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:cardCornerRadius="15dp"
app:cardElevation="5dp"
app:cardUseCompatPadding="true">

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

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

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Name : " />

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

</LinearLayout>

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

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Icon : " />

<ImageView
android:id="@+id/tvIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:padding="10dp"
android:text="" />

</LinearLayout>

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

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="Id : " />

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

</LinearLayout>

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

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="SearchUrl : " />

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

</LinearLayout>

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

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="NativeUrl : " />

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

</LinearLayout>

</LinearLayout>

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

layout.button_two

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

<ImageView
android:id="@+id/imgButton"
android:layout_width="50dp"
android:layout_height="50dp" />
</LinearLayout>

Now you need to create two RecyclerView.ViewHolder for your both viewType

Now you need to Override getItemViewType()

  • it Return the viewType of the item at position for the purposes of view recycling.

Now in your onCreateViewHolder() method you need to return your instance of your ViewHolder based on your viewType which you will get using getItemViewType() method

Than in your onBindViewHolder() method based your viewType set your view property

here is the sample code of RecyclerView.Adapter with multiple view types

DataAdapter

import android.content.Context;
import android.support.annotation.NonNull;
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;

public class DataAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private Context context;
ArrayList<Bookmark> arrayList = new ArrayList<>();
public static final int ITEM_TYPE_ONE = 0;
public static final int ITEM_TYPE_TWO = 1;

public DataAdapter(Context context, ArrayList<Bookmark> arrayList) {
this.context = context;
this.arrayList = arrayList;
}

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

View view = null;
// check here the viewType and return RecyclerView.ViewHolder based on view type
if (viewType == ITEM_TYPE_ONE) {
view = LayoutInflater.from(context).inflate(R.layout.layout_one, parent, false);
return new ViewHolder(view);
} else if (viewType == ITEM_TYPE_TWO) {
view = LayoutInflater.from(context).inflate(R.layout.button_two, parent, false);
return new ButtonViewHolder(view);
}else {
return null;
}

}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

final int itemType = getItemViewType(position);
// First check here the View Type
// than set data based on View Type to your recyclerview item
if (itemType == ITEM_TYPE_ONE) {
ViewHolder viewHolder = (ViewHolder) holder;
viewHolder.tvName.setText(arrayList.get(position).getName());
viewHolder.tvIcon.setImageResource(arrayList.get(position).getIcon());
viewHolder.tvSearchUrl.setText(arrayList.get(position).getSearchUrl());
viewHolder.tvNativeUrl.setText(arrayList.get(position).getNativeUrl());
} else if (itemType == ITEM_TYPE_TWO) {
ButtonViewHolder buttonViewHolder = (ButtonViewHolder) holder;
buttonViewHolder.imgButton.setImageResource(arrayList.get(position).getIcon());
}

}

@Override
public int getItemViewType(int position) {
// based on you list you will return the ViewType
if (arrayList.get(position).getViewType() == 0) {
return ITEM_TYPE_ONE;
} else {
return ITEM_TYPE_TWO;
}
}

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

public class ViewHolder extends RecyclerView.ViewHolder {

TextView tvName, tvId, tvSearchUrl, tvNativeUrl;

ImageView tvIcon;

public ViewHolder(@NonNull View itemView) {
super(itemView);

tvName = itemView.findViewById(R.id.tvName);
tvIcon = itemView.findViewById(R.id.tvIcon);
tvId = itemView.findViewById(R.id.tvId);
tvSearchUrl = itemView.findViewById(R.id.tvSearchUrl);
tvNativeUrl = itemView.findViewById(R.id.tvNativeUrl);
}
}

public class ButtonViewHolder extends RecyclerView.ViewHolder {


ImageView imgButton;

public ButtonViewHolder(@NonNull View itemView) {
super(itemView);

imgButton = itemView.findViewById(R.id.imgButton);

}
}
}

When you adding data in your list you need to provide the viewtype in list

Make some changes in your Bookmark POJO class

Bookmark POJO class

public class Bookmark
{
String name,id,nativeUrl,searchUrl;
int icon;

int viewType;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public int getIcon() {
return icon;
}

public void setIcon(int icon) {
this.icon = icon;
}

public String getNativeUrl() {
return nativeUrl;
}

public void setNativeUrl(String nativeUrl) {
this.nativeUrl = nativeUrl;
}

public String getSearchUrl() {
return searchUrl;
}

public void setSearchUrl(String searchUrl) {
this.searchUrl = searchUrl;
}

public int getViewType() {
return viewType;
}

public void setViewType(int viewType) {
this.viewType = viewType;
}

@Override
public String toString() {
return "Bookmark{" +
"name='" + name + '\'' +
", icon='" + icon + '\'' +
", id='" + id + '\'' +
", nativeUrl='" + nativeUrl + '\'' +
", searchUrl='" + searchUrl + '\'' +
'}';
}
}

Sample activity code

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

private Context mContext;
ArrayList<Bookmark> arrayList = new ArrayList<>();

RecyclerView myRecyclerView;
DataAdapter dataAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


mContext = this;

myRecyclerView = findViewById(R.id.myRecyclerView);
myRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
myRecyclerView.setHasFixedSize(true);

dataAdapter = new DataAdapter(mContext, arrayList);
myRecyclerView.setAdapter(dataAdapter);

try {

XmlPullParser xpp = getResources().getXml(R.xml.bookmarks);

while (xpp.getEventType() != XmlPullParser.END_DOCUMENT) {
if (xpp.getEventType() == XmlPullParser.START_TAG) {
if (xpp.getName().equals("Bookmark")) {

Log.e("MY_VALUE", " * " + xpp.getAttributeValue(0) + " * ");
Log.e("MY_VALUE", " * " + xpp.getAttributeValue(1) + " * ");
Log.e("MY_VALUE", " * " + xpp.getAttributeValue(5) + " * ");
Log.e("MY_VALUE", " * " + xpp.getAttributeValue(2) + " * ");
Log.e("MY_VALUE", " * " + xpp.getAttributeValue(3) + " * ");
Log.e("MY_VALUE", " * " + xpp.getAttributeValue(4) + " * ");


Bookmark bookmark = new Bookmark();
bookmark.setName(xpp.getAttributeValue(0));

int drawableResourceId = this.getResources().getIdentifier(xpp.getAttributeValue(1), "drawable", mContext.getPackageName());
bookmark.setIcon(drawableResourceId);

bookmark.setId(xpp.getAttributeValue(2));

bookmark.setSearchUrl(xpp.getAttributeValue(3));
bookmark.setNativeUrl(xpp.getAttributeValue(4));

// here you need to set view type
bookmark.setViewType(0);
arrayList.add(bookmark);
}
}

xpp.next();
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

// here i have added second viewType
// you need to set as per your requirement
Bookmark bookmark = new Bookmark();
bookmark.setViewType(1);
bookmark.setIcon(R.drawable.dishu);
arrayList.add(bookmark);
dataAdapter.notifyDataSetChanged();

}


}

NOTE

In the below code i have set second viewType at the last index of Arraylist
you need to set viewType as per your requirement

For more information you can check below articles

  • Working with RecyclerView and multiple view types
  • A RecyclerView with multiple item types
  • Android RecyclerView with Different Child Layouts
  • Android Pagination Tutorial—Handling Multiple View Types
  • Heterogenous Layouts inside RecyclerView
  • Android RecyclerView Example – Multiple ViewTypes
  • How to create RecyclerView with multiple view type?
  • Android Multiple row layout using RecyclerView

Android Studio Kotlin: RecyclerView with multiple viewtypes with different data

Assign default values to your parameters:

class DataModel(val itemName: String, 
val itemQuantity: String,
val itemPrice: String,
val topping1: String = "",
val topping2: String = "",
val topping3: String = "",
val topping4: String = "",
val viewType: Int) {
}

you can refactor this to be a data class

data class DataModel(val itemName: String,
val itemQuantity: String,
val itemPrice: String,
val topping1: String = "",
val topping2: String = "",
val topping3: String = "",
val topping4: String = "",
val viewType: Int)

you could also make use of nullable variables here, since they are potentially null:

data class DataModel(val itemName: String,
val itemQuantity: String,
val itemPrice: String,
val topping1: String? = null,
val topping2: String? = null,
val topping3: String? = null,
val topping4: String? = null,
val viewType: Int)

Then, because kotlin has named arguments, you can do this:

  val foo = DataModel("American","1","12.50", viewType = ItemAdapter.NO_TOPPING ) 


Related Topics



Leave a reply



Submit