Android RecyclerView 多类型, 多种 Item

Android RecyclerView 多类型:从一个列表到多种 Item 的完整实践


本文主要讲解 Android 中 RecyclerView 多类型 Item 的实现思路,从普通单类型 RecyclerView 出发,逐步过渡到多 ViewHolder、多布局的完整案例,并总结常见坑和实际项目中的组织方式。

适合人群:已经会使用普通 RecyclerView,但还不清楚 getItemViewType()、多 ViewHolder 怎么写、实际项目怎么组织的 Android 初学者。

文章目录

  • [Android RecyclerView 多类型:从一个列表到多种 Item 的完整实践](#Android RecyclerView 多类型:从一个列表到多种 Item 的完整实践)
    • [一、前言:为什么需要多类型 RecyclerView?](#一、前言:为什么需要多类型 RecyclerView?)
    • [二、先回顾普通 RecyclerView](#二、先回顾普通 RecyclerView)
    • [三、问题来了:如果 Item 不一样怎么办?](#三、问题来了:如果 Item 不一样怎么办?)
    • [四、多类型 RecyclerView 的核心思想](#四、多类型 RecyclerView 的核心思想)
    • [五、完整案例:文本、图片、广告三种 Item](#五、完整案例:文本、图片、广告三种 Item)
      • [1. 定义 Item 类型常量](#1. 定义 Item 类型常量)
      • [2. 设计数据模型](#2. 设计数据模型)
      • [3. 准备三种 Item 布局](#3. 准备三种 Item 布局)
        • [普通文本 item_text.xml](#普通文本 item_text.xml)
        • [图片 Item item_image.xml](#图片 Item item_image.xml)
        • [广告 Item item_ad.xml](#广告 Item item_ad.xml)
      • [4. 编写 Adapter 和三种 ViewHolder](#4. 编写 Adapter 和三种 ViewHolder)
      • [5. Activity 中使用](#5. Activity 中使用)
    • [六、最关键的 getItemViewType()](#六、最关键的 getItemViewType())
    • [七、position 和 viewType 不是一回事](#七、position 和 viewType 不是一回事)
    • [八、RecyclerView 到底是怎么运行的?](#八、RecyclerView 到底是怎么运行的?)
    • 九、常见坑与注意事项
      • [1. RecyclerView 不显示](#1. RecyclerView 不显示)
      • [2. viewType 可以随便写吗?](#2. viewType 可以随便写吗?)
      • [3. 为什么要抛出异常?](#3. 为什么要抛出异常?)
      • [4. 不要用多个 RecyclerView 模拟多类型](#4. 不要用多个 RecyclerView 模拟多类型)
    • [十、实际项目中怎么组织多类型 RecyclerView?](#十、实际项目中怎么组织多类型 RecyclerView?)

一、前言:为什么需要多类型 RecyclerView?

在实际 Android 项目中,RecyclerView 很少永远只有一种布局。

例如一个新闻 App首页有很多模块的内容, 可能有普通新闻, 带大图的新闻, 广告, 还有推荐内容等. 如果 RecyclerView 只能使用一种 Item 布局,那么这种需求就会变得很麻烦。所以这一篇主要学习 RecyclerView 如何在同一个列表中显示不同类型的 Item?


二、先回顾普通 RecyclerView

在学习多类型之前,先回忆一下普通 RecyclerView。例如我们有一个用户列表:

RecyclerView 会不断重复利用 Item。Adapter 中最重要的几个方法, 可以简单理解成下面的表格

方法 作用
onCreateViewHolder() 我要创建一个什么样子的 Item?
onBindViewHolder() 这个 Item 里面应该显示什么数据?
getItemCount() 总共有多少个 Item?

三、问题来了:如果 Item 不一样怎么办?

我们希望 RecyclerView 最终显示:

text 复制代码
┌─────────────────────┐
│ 普通文本             │
└─────────────────────┘

┌─────────────────────┐
│        图片          │
│     图片标题         │
└─────────────────────┘

┌─────────────────────┐
│        广告          │
└─────────────────────┘

┌─────────────────────┐
│ 普通文本             │
└─────────────────────┘

这时候问题就出现了, onCreateViewHolder() 到底应该创建哪一种布局?我们需要根据 Item 的类型决定。这就是多类型 RecyclerView 的核心。


四、多类型 RecyclerView 的核心思想

多类型 RecyclerView 并不是出现了什么新的 RecyclerView。它本质上还是RecyclerView->Adapter->ViewHolder

只不过:一个 Adapter 不再只有一种 ViewHolder,而是拥有多个 ViewHolder

Adapter

├── TextViewHolder

├── ImageViewHolder

└── AdViewHolder

而 RecyclerView 会通过一个叫 getItemViewType() 的方法判断: 当前位置的 Item 属于什么类型?整体流程如下所示, 核心是通过判断Item的类型进行后续不同的操作

position

getItemViewType(position)

得到 Item 类型

onCreateViewHolder()

创建对应 ViewHolder

onBindViewHolder()

绑定对应数据


五、完整案例:文本、图片、广告三种 Item

下面做一个简单但完整的例子。

RecyclerView 中有三种 Item:

TYPE_TEXT 普通文本

TYPE_IMAGE 图片 + 标题

TYPE_AD 广告

1. 定义 Item 类型常量

在 Adapter 中定义三个常量:

java 复制代码
public static final int TYPE_TEXT = 0;
public static final int TYPE_IMAGE = 1;
public static final int TYPE_AD = 2;

不要直接写数字,例如 0、1、2。 使用常量可读性更好,也更方便维护。


2. 设计数据模型

这里有一个非常重要的思想:Item 类型最好成为数据的一部分。

java 复制代码
public class ItemData {
    private final int type;
    private final String title;

    public ItemData(int type, String title) {
        this.type = type;
        this.title = title;
    }

    public int getType() {
        return type;
    }

    public String getTitle() {
        return title;
    }
}

这样我们就可以通过下面的方法创建变量添加到list中

java 复制代码
List<ItemData> dataList = new ArrayList<>();
dataList.add(new ItemData(MultiTypeAdapter.TYPE_TEXT, "这是普通文本"));
dataList.add(new ItemData(MultiTypeAdapter.TYPE_IMAGE, "这是图片新闻"));
dataList.add(new ItemData(MultiTypeAdapter.TYPE_AD, "这是一个广告"));
dataList.add(new ItemData(MultiTypeAdapter.TYPE_TEXT, "第二条普通文本"));

数据本身已经告诉 Adapter这个 Item 是什么类型;这个 Item 的内容是什么。


3. 准备三种 Item 布局

普通文本 item_text.xml
xml 复制代码
<?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="60dp"
    android:gravity="center_vertical"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp" />
</LinearLayout>
图片 Item item_image.xml
xml 复制代码
<?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="wrap_content"
    android:orientation="vertical"
    android:padding="16dp">

    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:background="#CCCCCC"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:textSize="16sp" />
</LinearLayout>

这里为了让案例聚焦多类型,图片 Item 暂时不加载网络图片,只设置一个灰色占位背景。

广告 Item item_ad.xml
xml 复制代码
<?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="80dp"
    android:background="#EEEEEE"
    android:gravity="center">

    <TextView
        android:id="@+id/tv_ad"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="广告"
        android:textSize="18sp" />
</LinearLayout>

4. 编写 Adapter 和三种 ViewHolder

完整 Adapter 如下。

java 复制代码
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

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

    public static final int TYPE_TEXT = 0;
    public static final int TYPE_IMAGE = 1;
    public static final int TYPE_AD = 2;

    private final List<ItemData> dataList;

    public MultiTypeAdapter(List<ItemData> dataList) {
        this.dataList = dataList;
    }

    @Override
    public int getItemViewType(int position) {
        return dataList.get(position).getType();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());

        switch (viewType) {
            case TYPE_TEXT:
                return new TextViewHolder(
                        inflater.inflate(R.layout.item_text, parent, false)
                );

            case TYPE_IMAGE:
                return new ImageViewHolder(
                        inflater.inflate(R.layout.item_image, parent, false)
                );

            case TYPE_AD:
                return new AdViewHolder(
                        inflater.inflate(R.layout.item_ad, parent, false)
                );

            default:
                throw new IllegalArgumentException("Unknown viewType: " + viewType);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ItemData item = dataList.get(position);
        int viewType = holder.getItemViewType();

        switch (viewType) {
            case TYPE_TEXT:
                ((TextViewHolder) holder).tvTitle.setText(item.getTitle());
                break;

            case TYPE_IMAGE:
                ((ImageViewHolder) holder).tvTitle.setText(item.getTitle());
                break;

            case TYPE_AD:
                ((AdViewHolder) holder).tvAd.setText(item.getTitle());
                break;

            default:
                throw new IllegalArgumentException("Unknown viewType: " + viewType);
        }
    }

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

    static class TextViewHolder extends RecyclerView.ViewHolder {
        TextView tvTitle;

        public TextViewHolder(View itemView) {
            super(itemView);
            tvTitle = itemView.findViewById(R.id.tv_title);
        }
    }

    static class ImageViewHolder extends RecyclerView.ViewHolder {
        ImageView ivImage;
        TextView tvTitle;

        public ImageViewHolder(View itemView) {
            super(itemView);
            ivImage = itemView.findViewById(R.id.iv_image);
            tvTitle = itemView.findViewById(R.id.tv_title);
        }
    }

    static class AdViewHolder extends RecyclerView.ViewHolder {
        TextView tvAd;

        public AdViewHolder(View itemView) {
            super(itemView);
            tvAd = itemView.findViewById(R.id.tv_ad);
        }
    }
}

到这里,一个最基本的多类型 RecyclerView 就完成了。


5. Activity 中使用

activity_main.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

MainActivity.java

java 复制代码
import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

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

        RecyclerView recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        List<ItemData> dataList = new ArrayList<>();
        dataList.add(new ItemData(MultiTypeAdapter.TYPE_TEXT, "这是第一条文本"));
        dataList.add(new ItemData(MultiTypeAdapter.TYPE_IMAGE, "这是一条图片新闻"));
        dataList.add(new ItemData(MultiTypeAdapter.TYPE_AD, "这里是广告"));
        dataList.add(new ItemData(MultiTypeAdapter.TYPE_TEXT, "这是第二条文本"));

        MultiTypeAdapter adapter = new MultiTypeAdapter(dataList);
        recyclerView.setAdapter(adapter);
    }
}

运行之后:

普通文本

图片新闻

广告

普通文本

不同的 Item 就可以同时存在于同一个 RecyclerView 中。


六、最关键的 getItemViewType()

现在进入多类型 RecyclerView 最重要的地方。

java 复制代码
@Override
public int getItemViewType(int position) {
    return dataList.get(position).getType();
}

看起来非常简单。但是这行代码实际上完成了一件非常重要的事情, 那就是读取Item返回类型数据


七、position 和 viewType 不是一回事

这是刚开始学习多类型 RecyclerView 时非常容易混淆的地方。

position type
0 TEXT
1 IMAGE
2 AD
3 TEXT
4 IMAGE

position 表示当前 Item 在列表中的位置。而 viewType 表示当前 Item 应该使用什么样的布局。

所以position = 3并不意味着viewType = 3


八、RecyclerView 到底是怎么运行的?

现在可以把整个过程完整串起来。

假设:

dataList:

0 → TEXT

1 → IMAGE

2 → AD

3 → TEXT

RecyclerView 需要显示 position = 1首先调用:getItemViewType(1). 然后调用onCreateViewHolder(parent, TYPE_IMAGE). 通过Adapter创建ImageViewHolder

然后是绑定数据:onBindViewHolder(holder, 1)

最终显示出图片新闻. 整个过程可以记成:

position

getItemViewType()

viewType

onCreateViewHolder()

ViewHolder

onBindViewHolder()

数据

这就是多类型 RecyclerView 的核心运行逻辑。


九、常见坑与注意事项

1. RecyclerView 不显示

最常见原因:

java 复制代码
recyclerView.setLayoutManager(new LinearLayoutManager(this));

忘记设置 LayoutManager。RecyclerView 如果没有 LayoutManager,是不会显示内容的。


2. viewType 可以随便写吗?

可以使用0、1、2、3

但是更推荐下面的宏定义写法, 这样代码可读性明显更好。

java 复制代码
public static final int TYPE_TEXT = 0;
public static final int TYPE_IMAGE = 1;
public static final int TYPE_AD = 2;

3. 为什么要抛出异常?

java 复制代码
throw new IllegalArgumentException("Unknown viewType: " + viewType);

因为如果未来增加了一种类型,却忘记在 Adapter 中处理,程序应该尽早告诉你这个 viewType 没有对应的 ViewHolder。


4. 不要用多个 RecyclerView 模拟多类型

很多初学者看到不同布局之后,第一反应可能是:

text 复制代码
RecyclerView 1 → 第一种 Item
RecyclerView 2 → 第二种 Item
RecyclerView 3 → 第三种 Item

通常没必要。

一个 RecyclerView 可以管理多种 Item。

这正是 getItemViewType() 存在的意义。


十、实际项目中怎么组织多类型 RecyclerView?

当类型少的时候,一个 Adapter 加几个 ViewHolder 就够用。

但如果一个真实项目有10 种, 20 种或30 种

继续写:

java 复制代码
if (viewType == TYPE_TEXT) {
    ...
} else if (viewType == TYPE_IMAGE) {
    ...
} else if (viewType == TYPE_AD) {
    ...
} else if (...) {
    ...
}

Adapter 会越来越难维护。所以实际项目通常会进一步进行封装。常见组织方式如下

简单项目

一个 Adapter

├── TextViewHolder

├── ImageViewHolder

└── AdViewHolder

适合类型少、逻辑简单的页面。

中等项目

Adapter

ItemBinder / Delegate

├── TextBinder

├── ImageBinder

└── AdBinder

每种类型独立管理自己的创建和绑定逻辑。

复杂项目

可以结合:

  • ListAdapter
  • DiffUtil
  • ViewBinding
  • ConcatAdapter
  • 分页库 Paging

让 Adapter 只负责分发,具体 Item 逻辑拆出去。


如果这篇文章对你有帮助,欢迎点赞、收藏、关注。后续会继续更新 RecyclerView 点击事件、DiffUtil、ListAdapter、ViewBinding 以及仿写小红书首页相关内容。

相关推荐
程序员yu1 小时前
数智码力:Python作用域完全梳理,全局变量局部变量不再混乱
开发语言·python·学习
sarasuki1 小时前
如何让 Agent 获取更多的能力?插件 / 技能系统
人工智能·设计模式·agent
silianpan2 小时前
CAD 文档预览 UTS 插件
android·ios·harmonyos
小麦在野2 小时前
独立 App 开发系列:用 GitHub Pages 托管隐私政策和用户协议
android·github
JMchen2 小时前
Android自定义View三大核心流程:measure、layout、draw完全解密
android·android studio
不灭的黄金瞳1232 小时前
Java数据类型与变量
java·开发语言·intellij-idea
silianpan2 小时前
Office 文档预览 UTS 插件
android·微信小程序·harmonyos
明君6892 小时前
一套专注 Android 逆向分析的 Skills
android·android逆向·agents·skills
小白学大数据2 小时前
Python 项目实战:用 Flask 构建生产级 MySQL 增删改查 REST API
开发语言·人工智能·python·mysql·flask