Question
RecyclerView Multiple View Types in Android (Java)
Question
I am using RecyclerView.Adapter in Android, where the adapter is typically written with a single ViewHolder type, like this:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private String[] mDataset;
public MyAdapter(String[] myDataset) {
mDataset = myDataset;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
public ViewHolder(TextView v) {
super(v);
mTextView = v;
}
}
@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.some_layout, parent, false);
ViewHolder vh = new ViewHolder((TextView) v);
return vh;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.mTextView.setText(mDataset[position]);
}
@Override
public int getItemCount() {
return mDataset.length;
}
}
Is it possible to create a RecyclerView that supports multiple view types, where different rows use different layouts and possibly different ViewHolder classes?
Short Answer
By the end of this page, you will understand how a RecyclerView can display different row layouts in the same list. You will learn how getItemViewType() works, how to create different ViewHolder classes, and how to inflate and bind the correct layout for each item in Java.
Concept
Yes, a RecyclerView can absolutely support multiple view types.
The key idea is that a list item does not have to use the same layout for every row. Some items might be headers, some might be messages, some might be loading indicators, and others might be normal content rows.
In Android, this is done by:
- Overriding
getItemViewType(int position) - Returning a type value for each item
- Checking that type inside
onCreateViewHolder(...) - Creating the correct
ViewHolderand inflating the correct layout - Binding the right data in
onBindViewHolder(...)
This matters because real apps rarely show perfectly uniform lists. Common examples include:
- chat apps with sent and received messages
- feeds with posts, ads, and loading rows
- forms with different input row types
- settings screens with switches, text rows, and section headers
A RecyclerView is designed to reuse item views efficiently. When you use multiple view types, Android keeps recycling views of the same type correctly, which preserves performance while giving you flexible UI design.
Mental Model
Think of a RecyclerView like a hotel with different room categories.
- Some guests need a single room
- Some need a double room
- Some need a suite
The hotel does not put every guest in the same kind of room. Instead, it first checks what type is needed, then prepares the correct room.
A RecyclerView works the same way:
getItemViewType(position)decides what kind of item this isonCreateViewHolder(...)creates the correct layout for that kindonBindViewHolder(...)fills it with data
So instead of asking, "How do I make one row design fit everything?", think, "How do I label each item with its type and give that type the right layout?"
Syntax and Examples
The core method for multiple view types is:
@Override
public int getItemViewType(int position) {
return ...; // return a constant representing the row type
}
A common pattern is to define constants:
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
Then return the correct type based on position or data.
Example: Header + normal items
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private final String[] items;
public MyAdapter(String[] items) {
this.items = items;
}
static class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView title;
HeaderViewHolder(View itemView) {
super(itemView);
title = itemView.findViewById(R.id.headerTitle);
}
}
static class ItemViewHolder extends RecyclerView.ViewHolder {
TextView text;
ItemViewHolder(View itemView) {
super(itemView);
text = itemView.findViewById(R.id.itemText);
}
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return TYPE_HEADER;
}
return TYPE_ITEM;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == TYPE_HEADER) {
View view = inflater.inflate(R.layout.row_header, parent, false);
return new HeaderViewHolder(view);
} else {
View view = inflater.inflate(R.layout.row_item, parent, false);
return new ItemViewHolder(view);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof HeaderViewHolder) {
HeaderViewHolder headerHolder = (HeaderViewHolder) holder;
headerHolder.title.setText("My List Header");
} else if (holder instanceof ItemViewHolder) {
ItemViewHolder itemHolder = (ItemViewHolder) holder;
itemHolder.text.setText(items[position]);
}
}
@Override
public int getItemCount() {
return items.length;
}
}
What is happening here?
- The adapter uses
RecyclerView.ViewHolderas the base type. - There are two specific
ViewHolderclasses. getItemViewType()tells the adapter what each row should be.onCreateViewHolder()inflates the correct XML layout.onBindViewHolder()casts the holder and binds the right data.
This is the standard approach for multiple layouts in one RecyclerView.
Step by Step Execution
Consider this small example:
@Override
public int getItemViewType(int position) {
return position == 0 ? TYPE_HEADER : TYPE_ITEM;
}
Suppose the list has 3 rows:
- position 0 → header
- position 1 → item
- position 2 → item
Step-by-step flow
1. RecyclerView asks for item count
getItemCount()
The adapter returns 3.
2. RecyclerView needs row at position 0
It calls:
getItemViewType(0)
This returns TYPE_HEADER.
3. RecyclerView creates a header ViewHolder
It calls:
onCreateViewHolder(parent, TYPE_HEADER)
The adapter inflates row_header.xml and returns HeaderViewHolder.
4. RecyclerView binds the header
It calls:
onBindViewHolder(holder, 0)
The adapter sees that holder is a HeaderViewHolder and sets the header text.
5. RecyclerView needs row at position 1
It calls:
getItemViewType(1)
This returns TYPE_ITEM.
6. RecyclerView creates an item ViewHolder
It calls:
onCreateViewHolder(parent, TYPE_ITEM)
The adapter inflates row_item.xml and returns ItemViewHolder.
7. RecyclerView binds the item
It calls:
onBindViewHolder(holder, 1)
The adapter casts to ItemViewHolder and sets the text for that row.
Important detail
A header ViewHolder is recycled only for header-type rows, and an item ViewHolder is recycled only for item-type rows. That is why the viewType value matters.
Real World Use Cases
Multiple view types are extremely common in Android apps.
Chat apps
- sent message layout
- received message layout
- date separator row
News or social feeds
- article card
- sponsored ad row
- loading spinner
- error retry row
E-commerce apps
- product item
- section header
- banner row
- empty state row
Settings screens
- plain text option
- switch toggle row
- category heading
- button row
Form builders
- text input field
- dropdown selector
- checkbox row
- validation message row
In all of these cases, one RecyclerView shows a mixed list of different UI blocks.
Real Codebase Usage
In real projects, developers usually do more than just switch on position. They often model each row as data with a type.
Common pattern: model-based item types
Instead of saying "position 0 is a header," a codebase may define item models like:
class ListItem {
int type;
String text;
}
Then getItemViewType(position) returns items.get(position).type.
This is better because it keeps UI logic connected to the actual data.
Common patterns in production
- Guard clauses: return early if data is missing
- Validation: ensure the item type is supported
- Constants or enums: avoid magic numbers like
0,1,2 - Base item models: store all rows in a single list
- Fallback handling: throw an error or log if an unknown type appears
Example:
@Override
public int getItemViewType(int position) {
if (position < 0 || position >= items.size()) {
throw new IllegalArgumentException("Invalid position: " + position);
}
return items.get(position).getType();
}
Another real-world pattern: loading rows
When fetching data from an API, teams often append a loading item at the bottom:
- normal content rows use one layout
- loading row uses a progress bar layout
- error row uses a retry button layout
That lets the list represent app state directly inside the adapter.
Common Mistakes
1. Using only one ViewHolder type for all rows
If different rows need different layouts, forcing them into one holder becomes messy and error-prone.
Problematic code
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
// hard to manage if rows are truly different
}
Better approach
Use:
RecyclerView.Adapter<RecyclerView.ViewHolder>
and create separate holder classes.
2. Forgetting to override getItemViewType()
If every row uses the default type, RecyclerView cannot distinguish between different layouts.
@Override
public int getItemViewType(int position) {
return TYPE_ITEM;
}
This would make all rows the same type, even when they are not.
3. Casting the wrong ViewHolder
Broken example
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
HeaderViewHolder h = (HeaderViewHolder) holder; // crashes if this is actually an item row
h.title.setText("Header");
}
Safer version
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof HeaderViewHolder) {
((HeaderViewHolder) holder).title.setText("Header");
}
}
4. Returning inconsistent types
If getItemViewType() says an item is a header, but onCreateViewHolder() inflates an item layout, your binding logic will break.
5. Using position-based logic when data changes often
If you rely only on position == 0, position == 1, and later insert or remove rows, the logic can become fragile.
A data-driven item type list is often more maintainable.
6. Inflating with the wrong parent settings
Use:
inflate(layoutId, parent, false)
not:
inflate(layoutId, null)
Using the parent helps Android apply proper layout parameters.
Comparisons
| Approach | Best for | Pros | Cons |
|---|---|---|---|
| Single view type | Simple lists where every row looks the same | Easy to write | Not flexible |
| Multiple view types | Mixed lists with different layouts | Powerful and realistic | More code to manage |
| Position-based view type logic | Fixed structures like one header at top | Quick to implement | Can break when list structure changes |
| Data-based view type logic | Dynamic lists from APIs or local models | Easier to maintain | Requires a better data model |
ListView vs RecyclerView for multiple types
| Feature | ListView | RecyclerView |
|---|---|---|
| Multiple view types support | Yes | Yes |
| ViewHolder pattern | Manual optimization | Built-in design pattern |
| Layout flexibility | More limited | More powerful |
| Recommended for modern Android | No | Yes |
One ViewHolder vs many ViewHolders
| Option | When to use | Notes |
|---|---|---|
| One ViewHolder | All rows share the same layout structure | Best for simple lists |
| Multiple ViewHolders | Rows have different UI elements | Best for mixed content lists |
Cheat Sheet
// 1. Define types
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
// 2. Return type for each position
@Override
public int getItemViewType(int position) {
return position == 0 ? TYPE_HEADER : TYPE_ITEM;
}
// 3. Create correct ViewHolder
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == TYPE_HEADER) {
View view = inflater.inflate(R.layout.row_header, parent, false);
return new HeaderViewHolder(view);
}
View view = inflater.inflate(R.layout.row_item, parent, false);
return new ItemViewHolder(view);
}
// 4. Bind correct ViewHolder
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof HeaderViewHolder) {
((HeaderViewHolder) holder).title.setText("Header");
} else if (holder instanceof ItemViewHolder) {
((ItemViewHolder) holder).text.setText(items[position]);
}
}
Rules to remember
- Override
getItemViewType()for mixed row layouts - Use different
ViewHolderclasses when layouts differ - Inflate layouts based on
viewType - Bind data based on the actual holder type
- Use
inflate(..., parent, false) - Prefer data-driven types for dynamic lists
- Keep type constants readable and consistent
FAQ
Can RecyclerView have different row layouts?
Yes. That is exactly what multiple view types are for. You return a type from getItemViewType() and inflate the matching layout.
Do I need a different ViewHolder for each layout?
Usually, yes. If layouts are meaningfully different, separate ViewHolder classes make the code cleaner and safer.
What does viewType mean in onCreateViewHolder()?
It is the value returned by getItemViewType(position). Android passes it back so you know which layout to create.
Can I use position to decide the type?
Yes, for simple cases like a fixed header. But for dynamic lists, using the item data itself is usually better.
Should I extend RecyclerView.Adapter<RecyclerView.ViewHolder> for multiple types?
Yes, that is a common approach because the adapter may return different ViewHolder subclasses.
Will RecyclerView still recycle views correctly with multiple types?
Yes. It recycles views by type, so a header row is reused for header rows and item rows are reused for item rows.
What happens if I return the wrong type?
You may inflate the wrong layout or cast the wrong holder type, which can cause crashes or incorrect UI.
Mini Project
Description
Build a RecyclerView adapter that shows a simple inbox-style list. The first row is a section header, and the remaining rows are message items. This demonstrates how to use multiple view types, different layouts, and different ViewHolder classes in one adapter.
Goal
Create a Java RecyclerView.Adapter that displays one header row and multiple message rows using two view types.
Requirements
- Define at least two item type constants.
- Override
getItemViewType(int position). - Create separate
ViewHolderclasses for the header row and message row. - Inflate a different layout for each row type.
- Bind the correct text to each row based on its type.
Keep learning
Related questions
Add External JAR Files to an IntelliJ IDEA Java Project
Learn how to add external JAR dependencies to an IntelliJ IDEA Java project using module libraries, and when to use Maven or Gradle instead.
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Call a Method After a Delay in Android Java
Learn how to run Java code after a delay in Android using Handler.postDelayed, manage the main thread, and cancel callbacks safely.