RecyclerView多布局详解

文章目录

  • RecyclerView多布局详解
    • [1. 引入:为什么需要多布局?](#1. 引入:为什么需要多布局?)
      • [1.1 单一布局的局限](#1.1 单一布局的局限)
      • [1.2 常见多布局场景](#1.2 常见多布局场景)
    • [2. 多布局的核心:getItemViewType](#2. 多布局的核心:getItemViewType)
      • [2.1 原理](#2.1 原理)
      • [2.2 重写getItemViewType](#2.2 重写getItemViewType)
    • [3. 完整实现:聊天列表多布局](#3. 完整实现:聊天列表多布局)
      • [3.1 定义布局类型常量](#3.1 定义布局类型常量)
      • [3.2 重写getItemViewType](#3.2 重写getItemViewType)
      • [3.3 创建不同的ViewHolder](#3.3 创建不同的ViewHolder)
      • [3.4 根据类型创建对应的ViewHolder](#3.4 根据类型创建对应的ViewHolder)
      • [3.5 根据ViewHolder类型绑定数据](#3.5 根据ViewHolder类型绑定数据)
      • [3.6 返回Item总数](#3.6 返回Item总数)
    • [4. 三种布局的XML](#4. 三种布局的XML)
      • [4.1 自己发的消息(item_chat_self.xml)](#4.1 自己发的消息(item_chat_self.xml))
      • [4.2 别人发的消息(item_chat_other.xml)](#4.2 别人发的消息(item_chat_other.xml))
      • [4.3 时间戳(item_chat_time.xml)](#4.3 时间戳(item_chat_time.xml))
    • [5. 多布局的优化技巧](#5. 多布局的优化技巧)
      • [5.1 用SparseArray管理布局ID](#5.1 用SparseArray管理布局ID)
      • [5.2 避免在onBindViewHolder中做耗时操作](#5.2 避免在onBindViewHolder中做耗时操作)
      • [5.3 多布局的局部刷新](#5.3 多布局的局部刷新)
    • [6. 多布局与缓存的关系](#6. 多布局与缓存的关系)
      • [6.1 ViewType相同才能复用](#6.1 ViewType相同才能复用)
      • [6.2 缓存池的容量](#6.2 缓存池的容量)
    • [7. 易错点总结](#7. 易错点总结)
    • [8. 总结](#8. 总结)

RecyclerView多布局详解

1. 引入:为什么需要多布局?

1.1 单一布局的局限

普通的RecyclerView列表,每个Item长一个样:

复制代码
┌─────────────────────┐
│  头像  用户名  内容  │
├─────────────────────┤
│  头像  用户名  内容  │
├─────────────────────┤
│  头像  用户名  内容  │
└─────────────────────┘

但实际开发中,列表往往需要多种样式的Item

复制代码
┌─────────────────────┐
│  公告:系统维护通知   │  ← 公告布局
├─────────────────────┤
│  头像  张三  你好    │  ← 聊天布局
├─────────────────────┤
│  头像  李四  在吗    │  ← 聊天布局
├─────────────────────┤
│  2026年9月19日      │  ← 时间戳布局
├─────────────────────┤
│  头像  王五  晚上好  │  ← 聊天布局
└─────────────────────┘

多布局就是让RecyclerView根据不同的数据类型,显示不同的Item样式。

1.2 常见多布局场景

场景 多布局需求
聊天列表 自己发的消息(右)、别人发的消息(左)、时间戳(中)
电商首页 轮播图、分类入口、商品列表、广告位
朋友圈 纯文字、带图、带视频、广告
设置页 普通选项、开关选项、分组标题
新闻列表 大图新闻、小图新闻、纯文字新闻

2. 多布局的核心:getItemViewType

2.1 原理

RecyclerView通过getItemViewType()方法来判断每个位置的Item应该用哪种布局:

复制代码
position 0 → viewType 0 → 加载布局A
position 1 → viewType 1 → 加载布局B
position 2 → viewType 0 → 加载布局A
position 3 → viewType 2 → 加载布局C

2.2 重写getItemViewType

java 复制代码
@Override
public int getItemViewType(int position) {
    // 根据position返回对应的类型
    if (position == 0) {
        return TYPE_HEADER;      // 头部
    } else if (dataList.get(position).getType() == MSG_SELF) {
        return TYPE_SELF;        // 自己发的消息
    } else if (dataList.get(position).getType() == MSG_OTHER) {
        return TYPE_OTHER;       // 别人发的消息
    } else {
        return TYPE_TIME;        // 时间戳
    }
}

3. 完整实现:聊天列表多布局

3.1 定义布局类型常量

java 复制代码
public class ChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    // 定义三种布局类型
    public static final int TYPE_SELF = 0;    // 自己发的消息
    public static final int TYPE_OTHER = 1;   // 别人发的消息
    public static final int TYPE_TIME = 2;    // 时间戳
    
    private List<ChatMessage> messageList;
    private Context context;
    
    public ChatAdapter(Context context, List<ChatMessage> messageList) {
        this.context = context;
        this.messageList = messageList;
    }
}

3.2 重写getItemViewType

java 复制代码
@Override
public int getItemViewType(int position) {
    ChatMessage message = messageList.get(position);
    
    if (message.isTime()) {
        return TYPE_TIME;       // 时间戳类型
    } else if (message.isSelf()) {
        return TYPE_SELF;       // 自己发的
    } else {
        return TYPE_OTHER;      // 别人发的
    }
}

3.3 创建不同的ViewHolder

java 复制代码
// 自己发的消息
static class SelfViewHolder extends RecyclerView.ViewHolder {
    TextView tvContent;
    ImageView ivAvatar;
    
    public SelfViewHolder(View itemView) {
        super(itemView);
        tvContent = itemView.findViewById(R.id.tv_content);
        ivAvatar = itemView.findViewById(R.id.iv_avatar);
    }
}

// 别人发的消息
static class OtherViewHolder extends RecyclerView.ViewHolder {
    TextView tvContent;
    TextView tvName;
    ImageView ivAvatar;
    
    public OtherViewHolder(View itemView) {
        super(itemView);
        tvContent = itemView.findViewById(R.id.tv_content);
        tvName = itemView.findViewById(R.id.tv_name);
        ivAvatar = itemView.findViewById(R.id.iv_avatar);
    }
}

// 时间戳
static class TimeViewHolder extends RecyclerView.ViewHolder {
    TextView tvTime;
    
    public TimeViewHolder(View itemView) {
        super(itemView);
        tvTime = itemView.findViewById(R.id.tv_time);
    }
}

3.4 根据类型创建对应的ViewHolder

java 复制代码
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(context);
    
    switch (viewType) {
        case TYPE_SELF:
            // 加载自己消息的布局
            View selfView = inflater.inflate(R.layout.item_chat_self, parent, false);
            return new SelfViewHolder(selfView);
            
        case TYPE_OTHER:
            // 加载别人消息的布局
            View otherView = inflater.inflate(R.layout.item_chat_other, parent, false);
            return new OtherViewHolder(otherView);
            
        case TYPE_TIME:
            // 加载时间戳的布局
            View timeView = inflater.inflate(R.layout.item_chat_time, parent, false);
            return new TimeViewHolder(timeView);
            
        default:
            throw new IllegalArgumentException("未知的viewType: " + viewType);
    }
}

3.5 根据ViewHolder类型绑定数据

java 复制代码
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    ChatMessage message = messageList.get(position);
    
    if (holder instanceof SelfViewHolder) {
        // 绑定自己消息的数据
        SelfViewHolder selfHolder = (SelfViewHolder) holder;
        selfHolder.tvContent.setText(message.getContent());
        // 加载头像...
        
    } else if (holder instanceof OtherViewHolder) {
        // 绑定别人消息的数据
        OtherViewHolder otherHolder = (OtherViewHolder) holder;
        otherHolder.tvContent.setText(message.getContent());
        otherHolder.tvName.setText(message.getSenderName());
        // 加载头像...
        
    } else if (holder instanceof TimeViewHolder) {
        // 绑定时间戳的数据
        TimeViewHolder timeHolder = (TimeViewHolder) holder;
        timeHolder.tvTime.setText(message.getTime());
    }
}

3.6 返回Item总数

java 复制代码
@Override
public int getItemCount() {
    return messageList.size();
}

4. 三种布局的XML

4.1 自己发的消息(item_chat_self.xml)

xml 复制代码
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:gravity="end"
    android:padding="10dp">
    
    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/bg_bubble_green"
        android:padding="10dp"
        android:textColor="#FFFFFF"
        android:maxWidth="240dp" />
    
    <ImageView
        android:id="@+id/iv_avatar"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginLeft="8dp"
        android:src="@drawable/avatar_self" />
        
</LinearLayout>

4.2 别人发的消息(item_chat_other.xml)

xml 复制代码
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="10dp">
    
    <ImageView
        android:id="@+id/iv_avatar"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/avatar_other" />
    
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_marginLeft="8dp">
        
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:textColor="#999999" />
        
        <TextView
            android:id="@+id/tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/bg_bubble_white"
            android:padding="10dp"
            android:maxWidth="240dp" />
    </LinearLayout>
    
</LinearLayout>

4.3 时间戳(item_chat_time.xml)

xml 复制代码
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:padding="10dp">
    
    <TextView
        android:id="@+id/tv_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#E0E0E0"
        android:padding="4dp 8dp"
        android:textSize="12sp"
        android:textColor="#666666" />
        
</LinearLayout>

5. 多布局的优化技巧

5.1 用SparseArray管理布局ID

如果布局类型很多,用switch-case写起来很臃肿,可以用SparseArray优化:

java 复制代码
public class ChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    // 用SparseArray存布局ID
    private SparseArray<Integer> layoutIds = new SparseArray<>();
    
    public ChatAdapter() {
        layoutIds.put(TYPE_SELF, R.layout.item_chat_self);
        layoutIds.put(TYPE_OTHER, R.layout.item_chat_other);
        layoutIds.put(TYPE_TIME, R.layout.item_chat_time);
    }
    
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // 直接从SparseArray拿布局ID
        int layoutId = layoutIds.get(viewType);
        View view = LayoutInflater.from(context).inflate(layoutId, parent, false);
        
        // 根据viewType创建对应的ViewHolder
        if (viewType == TYPE_SELF) return new SelfViewHolder(view);
        if (viewType == TYPE_OTHER) return new OtherViewHolder(view);
        return new TimeViewHolder(view);
    }
}

5.2 避免在onBindViewHolder中做耗时操作

java 复制代码
// 错误:每次绑定都加载图片
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    Glide.with(context).load(url).into(holder.ivAvatar); // 每次都加载
}

// 正确:用图片加载框架自动处理缓存
// Glide/Coil 自带缓存机制,但注意不要在onBind中做复杂计算

5.3 多布局的局部刷新

java 复制代码
// 只刷新指定类型的Item
public void updateTimeItem(int position) {
    notifyItemChanged(position, PAYLOAD_TIME);
}

@Override
public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
    if (payloads.isEmpty()) {
        // 全量刷新
        onBindViewHolder(holder, position);
    } else {
        // 局部刷新
        for (Object payload : payloads) {
            if (PAYLOAD_TIME.equals(payload)) {
                // 只更新时间戳
            }
        }
    }
}

6. 多布局与缓存的关系

6.1 ViewType相同才能复用

复制代码
ViewType 0 的Item → 只能复用 ViewType 0 的缓存
ViewType 1 的Item → 只能复用 ViewType 1 的缓存
ViewType 2 的Item → 只能复用 ViewType 2 的缓存

四级缓存中RecycledViewPool按ViewType分类存放,不同类型的View不能交叉复用。

6.2 缓存池的容量

java 复制代码
// 每种ViewType默认缓存5个
pool.setMaxRecycledViews(TYPE_SELF, 10);   // 自己消息缓存10个
pool.setMaxRecycledViews(TYPE_OTHER, 10);  // 别人消息缓存10个
pool.setMaxRecycledViews(TYPE_TIME, 3);    // 时间戳缓存3个就够了

7. 易错点总结

易错点 说明
getItemViewType返回错误类型 导致onCreateViewHolder加载错误的布局
onCreateViewHolder的switch漏了default 未知类型时崩溃
onBindViewHolder用错ViewHolder类型 instanceof判断错误导致空指针
相同ViewType的布局不一致 同一个ViewType必须用同一个布局
忘记设置LayoutManager RecyclerView不显示任何内容

8. 总结

核心步骤 说明
1. 定义ViewType常量 每种布局一个int类型
2. 重写getItemViewType 根据position返回类型
3. 重写onCreateViewHolder 根据类型加载不同布局
4. 重写onBindViewHolder 根据ViewHolder类型绑定数据

一句话总结 :多布局的核心就是getItemViewType()返回不同值,onCreateViewHolder()根据值加载不同布局,onBindViewHolder()根据ViewHolder类型绑定不同数据。缓存池按ViewType分类缓存,同类型才能复用。

相关推荐
v_34836087621 小时前
Android_8 zygote启动分析
android
消失的旧时光-19432 小时前
Android 系统层扫盲 11:JNI 到底是什么?Java / Kotlin 是怎么调用 C/C++ 的?
android·binder·zygote·system_server·aops
mmsx2 小时前
Android 地图几万要素卡成幻灯片?顶点缓冲批上传与显存复用
android·opengl·地图·栅格
北京自在科技12 小时前
谷歌 Find Hub 迎来新版本更新
android·安卓
古法安卓18 小时前
Android-休眠唤醒后onLocationChanged没有数据问题排查
android·java·android studio
Htr_21 小时前
Creem 2.0 使用指南:面向 AI 构建时代的资金平台
android·数据库·人工智能·ui·photoshop
wardenlzr1 天前
车机看门狗(Watchdog)为何会让整机硬重启
android·优化·车机
IT毕设实战小研1 天前
基于大数据的商场商铺数据分析与可视化的设计与实现
android·java·大数据·django·课程设计