Handling the rows / sections logic similar to iOS's UITableView is not as simple in Android as it is in iOS, however, when you use RecyclerView - the flexibility of what you can do is far greater.
In the end, it's all about how you figure out what type of view you're displaying in the Adapter. Once you got that figured out, it should be easy sailing (not really, but at least you'll have that sorted).
The Adapter exposes two methods which you should override:
getItemViewType(int position)
This method's default implementation will always return 0, indicating that there is only 1 type of view. In your case, it is not so, and so you will need find a way to assert which row corresponds to which view type. Unlike iOS, which manages this for you with rows and sections, here you will have only one index to rely on, and you'll need to use your developer skills to know when a position correlates to a section header, and when it correlates to a normal row.
createViewHolder(ViewGroup parent, int viewType)
You need to override this method anyway, but usually people just ignore the viewType parameter. According to the view type, you'll need to inflate the correct layout resource and create your view holder accordingly. The RecyclerView will handle recycling different view types in a way which avoids clashing of different view types.
If you're planning on using a default LayoutManager, such as LinearLayoutManager
, you should be good to go. If you're planning on making your own LayoutManager implementation, you'll need to work a bit harder. The only API you really have to work with is findViewByPosition(int position)
which gives a given view at a certain position. Since you'll probably want to lay it out differently depending on what type this view is, you have a few options:
Usually when using the ViewHolder pattern, you set the view's tag with the view holder. You could use this during runtime in the layout manager to find out what type the view is by adding a field in the view holder which expresses this.
Since you'll need a function which determines which position correlates to which view type, you might as well make this method globally accessible somehow (maybe a singleton class which manages the data?), and then you can simply query the same method according to the position.
Here's a code sample:
// in this sample, I use an object array to simulate the data of the list.
// I assume that if the object is a String, it means I should display a header with a basic title.
// If not, I assume it's a custom model object I created which I will use to bind my normal rows.
private Object[] myData;
public static final int ITEM_TYPE_NORMAL = 0;
public static final int ITEM_TYPE_HEADER = 1;
public class MyAdapter extends Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_NORMAL) {
View normalView = LayoutInflater.from(getContext()).inflate(R.layout.my_normal_row, null);
return new MyNormalViewHolder(normalView); // view holder for normal items
} else if (viewType == ITEM_TYPE_HEADER) {
View headerRow = LayoutInflater.from(getContext()).inflate(R.layout.my_header_row, null);
return new MyHeaderViewHolder(headerRow); // view holder for header items
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final int itemType = getItemViewType(position);
if (itemType == ITEM_TYPE_NORMAL) {
((MyNormalViewHolder)holder).bindData((MyModel)myData[position]);
} else if (itemType == ITEM_TYPE_HEADER) {
((MyHeaderViewHolder)holder).setHeaderText((String)myData[position]);
}
}
@Override
public int getItemViewType(int position) {
if (myData[position] instanceof String) {
return ITEM_TYPE_HEADER;
} else {
return ITEM_TYPE_NORMAL;
}
}
@Override
public int getItemCount() {
return myData.length;
}
}
Here's a sample of how these view holders should look like:
public MyHeaderViewHolder extends ViewHolder {
private TextView headerLabel;
public MyHeaderViewHolder(View view) {
super(view);
headerLabel = (TextView)view.findViewById(R.id.headerLabel);
}
public void setHeaderText(String text) {
headerLabel.setText(text);
}
}
public MyNormalViewHolder extends ViewHolder {
private TextView titleLabel;
private TextView descriptionLabel;
public MyNormalViewHolder(View view) {
super(view);
titleLabel = (TextView)view.findViewById(R.id.titleLabel);
descriptionLabel = (TextView)view.findViewById(R.id.descriptionLabel);
}
public void bindData(MyModel model) {
titleLabel.setText(model.getTitle());
descriptionLabel.setText(model.getDescription());
}
}
Of course, this sample assumes you've constructed your data source (myData) in a way that makes it easy to implement an adapter in this way. As an example, I'll show you how I'd construct a data source which shows a list of names, and a header for every time the 1st letter of the name changes (assume the list is alphabetized) - similar to how a contacts list would look like:
// Assume names & descriptions are non-null and have the same length.
// Assume names are alphabetized
private void processDataSource(String[] names, String[] descriptions) {
String nextFirstLetter = "";
String currentFirstLetter;
List<Object> data = new ArrayList<Object>();
for (int i = 0; i < names.length; i++) {
currentFirstLetter = names[i].substring(0, 1); // get the 1st letter of the name
// if the first letter of this name is different from the last one, add a header row
if (!currentFirstLetter.equals(nextFirstLetter)) {
nextFirstLetter = currentFirstLetter;
data.add(nextFirstLetter);
}
data.add(new MyModel(names[i], descriptions[i]));
}
myData = data.toArray();
}
This example comes to solve a fairly specific issue, but I hope this gives you a good overview on how to handle different row types in a recycler, and allows you make the necessary adaptations in your own code to fit your needs.
The trick is to create subclasses of ViewHolder and then cast them.
public class GroupViewHolder extends RecyclerView.ViewHolder {
TextView mTitle;
TextView mContent;
public GroupViewHolder(View itemView) {
super (itemView);
// init views...
}
}
public class ImageViewHolder extends RecyclerView.ViewHolder {
ImageView mImage;
public ImageViewHolder(View itemView) {
super (itemView);
// init views...
}
}
private static final int TYPE_IMAGE = 1;
private static final int TYPE_GROUP = 2;
And then, at runtime do something like this:
@Override
public int getItemViewType(int position) {
// here your custom logic to choose the view type
return position == 0 ? TYPE_IMAGE : TYPE_GROUP;
}
@Override
public void onBindViewHolder (ViewHolder viewHolder, int i) {
switch (viewHolder.getItemViewType()) {
case TYPE_IMAGE:
ImageViewHolder imageViewHolder = (ImageViewHolder) viewHolder;
imageViewHolder.mImage.setImageResource(...);
break;
case TYPE_GROUP:
GroupViewHolder groupViewHolder = (GroupViewHolder) viewHolder;
groupViewHolder.mContent.setText(...)
groupViewHolder.mTitle.setText(...);
break;
}
}
Hope it helps.
According to Gil great answer I solved by Overriding the getItemViewType as explained by Gil. His answer is great and have to be marked as correct. In any case, I add the code to reach the score:
In your recycler adapter:
@Override
public int getItemViewType(int position) {
int viewType = 0;
// add here your booleans or switch() to set viewType at your needed
// I.E if (position == 0) viewType = 1; etc. etc.
return viewType;
}
@Override
public FileViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 0) {
return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_layout_for_first_row, parent, false));
}
return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_other_rows, parent, false));
}
By doing this, you can set whatever custom layout for whatever row!
It is quite tricky but that much hard, just copy the below code and you are done
package com.yuvi.sample.main;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.yuvi.sample.R;
import java.util.List;
/**
* Created by yubraj on 6/17/15.
*/
public class NavDrawerAdapter extends RecyclerView.Adapter<NavDrawerAdapter.MainViewHolder> {
List<MainOption> mainOptionlist;
Context context;
private static final int TYPE_PROFILE = 1;
private static final int TYPE_OPTION_MENU = 2;
private int selectedPos = 0;
public NavDrawerAdapter(Context context){
this.mainOptionlist = MainOption.getDrawableDataList();
this.context = context;
}
@Override
public int getItemViewType(int position) {
return (position == 0? TYPE_PROFILE : TYPE_OPTION_MENU);
}
@Override
public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType){
case TYPE_PROFILE:
return new ProfileViewHolder(LayoutInflater.from(context).inflate(R.layout.row_profile, parent, false));
case TYPE_OPTION_MENU:
return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.row_nav_drawer, parent, false));
}
return null;
}
@Override
public void onBindViewHolder(MainViewHolder holder, int position) {
if(holder.getItemViewType() == TYPE_PROFILE){
ProfileViewHolder mholder = (ProfileViewHolder) holder;
setUpProfileView(mholder);
}
else {
MyViewHolder mHolder = (MyViewHolder) holder;
MainOption mo = mainOptionlist.get(position);
mHolder.tv_title.setText(mo.title);
mHolder.iv_icon.setImageResource(mo.icon);
mHolder.itemView.setSelected(selectedPos == position);
}
}
private void setUpProfileView(ProfileViewHolder mholder) {
}
@Override
public int getItemCount() {
return mainOptionlist.size();
}
public class MyViewHolder extends MainViewHolder{
TextView tv_title;
ImageView iv_icon;
public MyViewHolder(View v){
super(v);
this.tv_title = (TextView) v.findViewById(R.id.tv_title);
this.iv_icon = (ImageView) v.findViewById(R.id.iv_icon);
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Redraw the old selection and the new
notifyItemChanged(selectedPos);
selectedPos = getLayoutPosition();
notifyItemChanged(selectedPos);
}
});
}
}
public class ProfileViewHolder extends MainViewHolder{
TextView tv_name, login;
ImageView iv_profile;
public ProfileViewHolder(View v){
super(v);
this.tv_name = (TextView) v.findViewById(R.id.tv_profile);
this.iv_profile = (ImageView) v.findViewById(R.id.iv_profile);
this.login = (TextView) v.findViewById(R.id.tv_login);
}
}
public void trace(String tag, String message){
Log.d(tag , message);
}
public class MainViewHolder extends RecyclerView.ViewHolder {
public MainViewHolder(View v) {
super(v);
}
}
}
enjoy !!!!
We can achieve multiple view on single RecyclerView from below way :-
Dependencies on Gradle so add below code:-
compile 'com.android.support:cardview-v7:23.0.1'
compile 'com.android.support:recyclerview-v7:23.0.1'
RecyclerView in XML
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Activity Code
private RecyclerView mRecyclerView;
private CustomAdapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private String[] mDataset = {“Data - one ”, “Data - two”,
“Showing data three”, “Showing data four”};
private int mDatasetTypes[] = {DataOne, DataTwo, DataThree}; //view types
...
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
mLayoutManager = new LinearLayoutManager(MainActivity.this);
mRecyclerView.setLayoutManager(mLayoutManager);
//Adapter is created in the last step
mAdapter = new CustomAdapter(mDataset, mDataSetTypes);
mRecyclerView.setAdapter(mAdapter);
First XML
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/ten"
android:elevation="@dimen/hundered”
card_view:cardBackgroundColor=“@color/black“>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding=“@dimen/ten">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=“Fisrt”
android:textColor=“@color/white“ />
<TextView
android:id="@+id/temp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/ten"
android:textColor="@color/white"
android:textSize="30sp" />
</LinearLayout>
</android.support.v7.widget.CardView>
Second XML
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/ten"
android:elevation="100dp"
card_view:cardBackgroundColor="#00bcd4">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/ten">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=“DataTwo”
android:textColor="@color/white" />
<TextView
android:id="@+id/score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/ten"
android:textColor="#ffffff"
android:textSize="30sp" />
</LinearLayout>
</android.support.v7.widget.CardView>
Third XML
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/ten"
android:elevation="100dp"
card_view:cardBackgroundColor="@color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/ten">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=“DataThree” />
<TextView
android:id="@+id/headline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/ten"
android:textSize="25sp" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/ten"
android:id="@+id/read_more"
android:background="@color/white"
android:text=“Show More” />
</LinearLayout>
</android.support.v7.widget.CardView>
Now time to make adapter and this is main for showing different -2 view on same recycler view so please check this code focus fully :-
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
private static final String TAG = "CustomAdapter";
private String[] mDataSet;
private int[] mDataSetTypes;
public static final int dataOne = 0;
public static final int dataTwo = 1;
public static final int dataThree = 2;
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View v) {
super(v);
}
}
public class DataOne extends ViewHolder {
TextView temp;
public DataOne(View v) {
super(v);
this.temp = (TextView) v.findViewById(R.id.temp);
}
}
public class DataTwo extends ViewHolder {
TextView score;
public DataTwo(View v) {
super(v);
this.score = (TextView) v.findViewById(R.id.score);
}
}
public class DataThree extends ViewHolder {
TextView headline;
Button read_more;
public DataThree(View v) {
super(v);
this.headline = (TextView) v.findViewById(R.id.headline);
this.read_more = (Button) v.findViewById(R.id.read_more);
}
}
public CustomAdapter(String[] dataSet, int[] dataSetTypes) {
mDataSet = dataSet;
mDataSetTypes = dataSetTypes;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View v;
if (viewType == dataOne) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.weather_card, viewGroup, false);
return new DataOne(v);
} else if (viewType == dataTwo) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.news_card, viewGroup, false);
return new DataThree(v);
} else {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.score_card, viewGroup, false);
return new DataTwo(v);
}
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
if (viewHolder.getItemViewType() == dataOne) {
DataOne holder = (DataOne) viewHolder;
holder.temp.setText(mDataSet[position]);
}
else if (viewHolder.getItemViewType() == dataTwo) {
DataThree holder = (DataTwo) viewHolder;
holder.headline.setText(mDataSet[position]);
}
else {
DataTwo holder = (DataTwo) viewHolder;
holder.score.setText(mDataSet[position]);
}
}
@Override
public int getItemCount() {
return mDataSet.length;
}
@Override
public int getItemViewType(int position) {
return mDataSetTypes[position];
}
}
You can check also this link for more information.
In my opinion,the starting point to create this kind of recyclerView is the knowledge of this method. Since this method is optional to override therefore it is not visible in RecylerView class by default which in turn makes many developers(including me) wonder where to begin. Once you know that this method exists, creating such RecyclerView would be a cakewalk.
You can create a RecyclerView
with any number of different Views(ViewHolders). But for better readability lets take an example of RecyclerView
with two Viewholders
.
Remember these 3 simple steps and you will be good to go.
getItemViewType(int position)
ViewType
in
onCreateViewHolder() methodPopulate View based on the itemViewType in onBindViewHolder()
method
Here is a code snippet for you
public class YourListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int LAYOUT_ONE= 0;
private static final int LAYOUT_TWO= 1;
@Override
public int getItemViewType(int position)
{
if(position==0)
return LAYOUT_ONE;
else
return LAYOUT_TWO;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view =null;
RecyclerView.ViewHolder viewHolder = null;
if(viewType==LAYOUT_ONE)
{
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.one,parent,false);
viewHolder = new ViewHolderOne(view);
}
else
{
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.two,parent,false);
viewHolder= new ViewHolderTwo(view);
}
return viewHolder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
if(holder.getItemViewType()== LAYOUT_ONE)
{
// Typecast Viewholder
// Set Viewholder properties
// Add any click listener if any
}
else {
ViewHolderOne vaultItemHolder = (ViewHolderOne) holder;
vaultItemHolder.name.setText(displayText);
vaultItemHolder.name.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
.......
}
});
}
}
/**************** VIEW HOLDER 1 ******************//
public class ViewHolderOne extends RecyclerView.ViewHolder {
public TextView name;
public ViewHolderOne(View itemView) {
super(itemView);
name = (TextView)itemView.findViewById(R.id.displayName);
}
}
//**************** VIEW HOLDER 2 ******************//
public class ViewHolderTwo extends RecyclerView.ViewHolder{
public ViewHolderTwo(View itemView) {
super(itemView);
..... Do something
}
}
}
Here is a project where I have implemented a RecyclerView with multiple ViewHolders.
You have to implement getItemViewType()
method in RecyclerView.Adapter
. By default onCreateViewHolder(ViewGroup parent, int viewType)
implementation viewType
of this method returns 0
. Firstly you need view type of the item at position for the purposes of view recycling and for that you have to override getItemViewType()
method in which you can pass viewType
which will return your position of item. Code sample is given below
@Override
public MyViewholder onCreateViewHolder(ViewGroup parent, int viewType) {
int listViewItemType = getItemViewType(viewType);
switch (listViewItemType) {
case 0: return new ViewHolder0(...);
case 2: return new ViewHolder2(...);
}
}
@Override
public int getItemViewType(int position) {
return position;
}
// and in the similar way you can set data according
// to view holder position by passing position in getItemViewType
@Override
public void onBindViewHolder(MyViewholder viewholder, int position) {
int listViewItemType = getItemViewType(position);
// ...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With