Question

RecyclerView Multiple View Types in Android (Java)

javaandroiduser-interfaceandroid-recyclerview

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:

  1. Overriding getItemViewType(int position)
  2. Returning a type value for each item
  3. Checking that type inside onCreateViewHolder(...)
  4. Creating the correct ViewHolder and inflating the correct layout
  5. 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 is
  • onCreateViewHolder(...) creates the correct layout for that kind
  • onBindViewHolder(...) 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?"

Take Quiz

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.ViewHolder as the base type.
  • There are two specific ViewHolder classes.
  • 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.

Take Quiz

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

ApproachBest forProsCons
Single view typeSimple lists where every row looks the sameEasy to writeNot flexible
Multiple view typesMixed lists with different layoutsPowerful and realisticMore code to manage
Position-based view type logicFixed structures like one header at topQuick to implementCan break when list structure changes
Data-based view type logicDynamic lists from APIs or local modelsEasier to maintainRequires a better data model

ListView vs RecyclerView for multiple types

FeatureListViewRecyclerView
Multiple view types supportYesYes
ViewHolder patternManual optimizationBuilt-in design pattern
Layout flexibilityMore limitedMore powerful
Recommended for modern AndroidNoYes

One ViewHolder vs many ViewHolders

OptionWhen to useNotes
One ViewHolderAll rows share the same layout structureBest for simple lists
Multiple ViewHoldersRows have different UI elementsBest for mixed content lists
Take Quiz

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 ViewHolder classes 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.

Take Quiz

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

  1. Define at least two item type constants.
  2. Override getItemViewType(int position).
  3. Create separate ViewHolder classes for the header row and message row.
  4. Inflate a different layout for each row type.
  5. Bind the correct text to each row based on its type.
Take Quiz

Keep learning

Related questions

View more