Как создать RecyclerView с разными типами элементов (multiple view types) в Android
Решение: Использование getItemViewType() и нескольких ViewHolder
Для создания RecyclerView с разными типами элементов нужно выполнить три ключевых шага:
- Определить константы для каждого типа элемента
- Переопределить метод
getItemViewType(), который возвращает тип для каждой позиции - В
onCreateViewHolder()использовать полученный viewType для создания нужного ViewHolder
1. Создайте базовую модель данных
Сначала нужно определить, какие типы данных будут в списке. Лучше всего использовать общий класс-родитель или интерфейс:
public class ListItem {
// Базовый класс для всех элементов
public static final int TYPE_HEADER = 0;
public static final int TYPE_MESSAGE = 1;
public static final int TYPE_IMAGE = 2;
private int type;
public ListItem(int type) {
this.type = type;
}
public int getType() { return type; }
}
// Конкретные классы-наследники
public class HeaderItem extends ListItem {
public String title;
public HeaderItem(String title) {
super(TYPE_HEADER);
this.title = title;
}
}
public class MessageItem extends ListItem {
public String text;
public MessageItem(String text) {
super(TYPE_MESSAGE);
this.text = text;
}
}
public class ImageItem extends ListItem {
public String imageUrl;
public ImageItem(String imageUrl) {
super(TYPE_IMAGE);
this.imageUrl = imageUrl;
}
}
2. Создайте адаптер с несколькими ViewHolder
public class MultiTypeAdapter extends RecyclerView.Adapter {
private List<ListItem> items;
public MultiTypeAdapter(List<ListItem> items) {
this.items = items;
}
@Override
public int getItemViewType(int position) {
// Возвращаем тип элемента на основе данных
return items.get(position).getType();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (viewType) {
case ListItem.TYPE_HEADER:
View headerView = inflater.inflate(R.layout.item_header, parent, false);
return new HeaderViewHolder(headerView);
case ListItem.TYPE_MESSAGE:
View messageView = inflater.inflate(R.layout.item_message, parent, false);
return new MessageViewHolder(messageView);
case ListItem.TYPE_IMAGE:
View imageView = inflater.inflate(R.layout.item_image, parent, false);
return new ImageViewHolder(imageView);
default:
throw new IllegalArgumentException("Unknown view type: " + viewType);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ListItem item = items.get(position);
// Определяем, с каким ViewHolder работаем и заполняем данными
if (holder instanceof HeaderViewHolder) {
HeaderItem headerItem = (HeaderItem) item;
((HeaderViewHolder) holder).title.setText(headerItem.title);
} else if (holder instanceof MessageViewHolder) {
MessageItem messageItem = (MessageItem) item;
((MessageViewHolder) holder).textView.setText(messageItem.text);
} else if (holder instanceof ImageViewHolder) {
ImageItem imageItem = (ImageItem) item;
// Загрузка изображения через Glide или Picasso
Glide.with(holder.itemView.getContext())
.load(imageItem.imageUrl)
.into(((ImageViewHolder) holder).imageView);
}
}
@Override
public int getItemCount() {
return items.size();
}
// Внутренние классы ViewHolder
static class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView title;
HeaderViewHolder(View itemView) {
super(itemView);
title = itemView.findViewById(R.id.header_title);
}
}
static class MessageViewHolder extends RecyclerView.ViewHolder {
TextView textView;
MessageViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.message_text);
}
}
static class ImageViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
ImageViewHolder(View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image_view);
}
}
}
3. Использование в Activity/Fragment
List<ListItem> items = new ArrayList<>();
items.add(new HeaderItem("Новости"));
items.add(new MessageItem("Сегодня отличная погода!"));
items.add(new ImageItem("https://example.com/image.jpg"));
items.add(new HeaderItem("Статьи"));
items.add(new MessageItem("Ещё одно сообщение"));
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
MultiTypeAdapter adapter = new MultiTypeAdapter(items);
recyclerView.setAdapter(adapter);
Важные моменты
- getItemViewType() вызывается перед onCreateViewHolder и onBindViewHolder, поэтому он должен работать быстро
- RecyclerView.ViewHolder используется как общий тип для всех холдеров
- Метод
onBindViewHolderпринимает базовый ViewHolder, поэтому нужно использоватьinstanceofдля проверки типа - Не забудьте создать отдельные layout-файлы для каждого типа (item_header.xml, item_message.xml, item_image.xml)
Этот подход позволяет легко добавлять новые типы элементов: просто добавьте новую константу, новый класс модели, новый ViewHolder и layout, и обработайте его в switch-case.
<<Альтернативный подход: использование единого ViewHolder с ViewStub
Если количество типов элементов невелико (2-3), можно использовать один ViewHolder, который динамически подставляет нужный layout через ViewStub. Это уменьшает количество классов, но делает код менее читаемым для сложных случаев.
public class CompactAdapter extends RecyclerView.Adapter<CompactAdapter.ViewHolder> {
private List<ListItem> items;
@Override
public int getItemViewType(int position) {
return items.get(position).getType();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_container, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
ListItem item = items.get(position);
// Активируем нужный ViewStub в зависимости от типа
holder.bind(item);
}
static class ViewHolder extends RecyclerView.ViewHolder {
ViewStub headerStub, messageStub, imageStub;
boolean headerInflated, messageInflated, imageInflated;
ViewHolder(View itemView) {
super(itemView);
headerStub = itemView.findViewById(R.id.stub_header);
messageStub = itemView.findViewById(R.id.stub_message);
imageStub = itemView.findViewById(R.id.stub_image);
}
void bind(ListItem item) {
if (item instanceof HeaderItem && !headerInflated) {
View inflatedView = headerStub.inflate();
// Настройка view
headerInflated = true;
}
// Аналогично для других типов
}
}
}
Плюсы: меньше классов, простая структура. Минусы: сложнее поддерживать при добавлении новых типов, возможны проблемы с производительностью при частой смене типов. Рекомендуется использовать первый подход для большинства случаев.