游乐游手机版
首页/AI教程/文章详情

Android从零开始完整实现RecyclerView多类型页面布局详细教程

时间:2026-06-06 16:46
安卓开发中多类型列表传统实现依赖视图类型区分,类型增多时代码臃肿、扩展性差。提出优雅封装方案:将不同类型项视为头部 底部视图,用哈希码代替类型标识,简化获取、创建和绑定三个关键方法,降低耦合度。

在 Android 开发中,RecyclerView 已成为列表展示的常用组件。但当列表需要承载多种不同类型的 item——例如首页的 Banner、广告、文本、图片、新闻混排时,实现难度会显著增加。不少开发者习惯通过 ViewType 进行区分,但在逻辑复杂、类型增多的情况下,这种方式往往导致代码臃肿、维护困难。下面我们将探讨这一问题,并介绍如何优雅地封装 Adapter。

01.先看看实际需求

以某个 APP 首页为例,它通常包含 Banner 区、广告区、文本内容、图片内容、新闻内容等模块。

RecyclerView 虽然可以通过 ViewType 区分不同 item 来满足需求,但仍存在一些明显的问题,例如:

RecyclerView实现多type页面

  • 当列表项过多、逻辑复杂时,Adapter 内部的代码量庞大,逻辑混乱,后期难以维护。
  • 每次新增列表都需要编写新的 Adapter,重复劳动,开发效率低下。
  • Adapter 无法复用:若多个页面包含多种类型,就必须为每个页面单独编写 Adapter。
  • 局部刷新较为麻烦。例如广告区本身是一个九宫格 RecyclerView,点击后需要局部刷新当前数据,实现起来比较繁琐。

02.adapter实现多个type

常见的多 Item 列表实现方式:根据不同的 ViewType 分别处理 item。如果业务逻辑复杂,该类代码量会非常庞大。版本迭代引入新需求时,修改代码费力,后期维护成本高。

主要操作步骤:

  • onCreateViewHolder 中依据 viewType 参数(即 getItemViewType 的返回值)判断需要创建的 ViewHolder 类型。
  • onBindViewHolder 方法中对 ViewHolder 的具体类型进行判断,分别为不同类型的 ViewHolder 绑定数据并处理逻辑。

代码如下所示:

public class HomePageAdapter extends RecyclerView.Adapter {
    public static final int TYPE_BANNER = 0;
    public static final int TYPE_AD = 1;
    public static final int TYPE_TEXT = 2;
    public static final int TYPE_IMAGE = 3;
    public static final int TYPE_NEW = 4;
    private List mData;

    public void setData(List data) { mData = data; }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType){
            case TYPE_BANNER:
                return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_banner_layout,null));
            case TYPE_AD:
                return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_ad_item_layout,null));
            case TYPE_TEXT:
                return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_text_item_layout,null));
            case TYPE_IMAGE:
                return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_image_item_layout,null));
            case TYPE_NEW:
                return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_news_item_layout,null));
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        int type = getItemViewType(position);
        switch (type){
            case TYPE_BANNER: // banner 逻辑处理
                break;
            case TYPE_AD: // 广告逻辑处理
                break;
            case TYPE_TEXT: // 文本逻辑处理
                break;
            case TYPE_IMAGE: // 图片逻辑处理
                break;
            case TYPE_NEW: // 视频逻辑处理
                break;
            // ... 此处省去N行代码
        }
    }

    @Override
    public int getItemViewType(int position) {
        if(position == 0){
            return TYPE_BANNER; // banner在开头
        }else {
            return mData.get(position).type; // type 的值为TYPE_AD,TYPE_IMAGE,TYPE_AD,等其中一个
        }
    }

    @Override
    public int getItemCount() {
        return mData == null ? 0 : mData.size();
    }

    public static class BannerViewHolder extends RecyclerView.ViewHolder{
        public BannerViewHolder(View itemView) { super(itemView); //绑定控件 }
    }
    public static class NewViewHolder extends RecyclerView.ViewHolder{
        public VideoViewHolder(View itemView) { super(itemView); //绑定控件 }
    }
    public static class AdViewHolder extends RecyclerView.ViewHolder{
        public AdViewHolder(View itemView) { super(itemView); //绑定控件 }
    }
    public static class TextViewHolder extends RecyclerView.ViewHolder{
        public TextViewHolder(View itemView) { super(itemView); //绑定控件 }
    }
    public static class ImageViewHolder extends RecyclerView.ViewHolder{
        public ImageViewHolder(View itemView) { super(itemView); //绑定控件 }
    }
}

03.这样写的弊端

上述实现方式存在三个显著缺陷:

  • 类型检查与类型转型:由于 onCreateViewHolder 中针对不同类型创建了各自的 ViewHolder,因此在 onBindViewHolder 中需要对不同 ViewHolder 进行数据绑定和逻辑处理,这必然导致使用 instanceof 进行类型检查与转型。
  • 不利于扩展:当前需求是列表中存在 5 种布局类型。但如果需求变化,极端情况下数据源来自服务器,由 model 决定列表布局类型。此时每当 model 变更或类型增多,都需要修改 Adapter 大量代码,并且 Adapter 必须知道特定 model 在列表中的位置(position)——除非与服务端约定 model 位置固定,这显然不现实。
  • 不利于维护:这是上一点的延伸。随着列表中布局类型的增加或调整,getItemViewTypeonCreateViewHolderonBindViewHolder 中的代码都需要相应变更或新增,导致 Adapter 代码臃肿混乱,维护成本不断提高。

04.如何优雅实现adapter封装

核心目标有三个:避免类的类型检查与类型转型、增强 Adapter 的扩展性、提升 Adapter 的可维护性。当列表中类型增加或减少时,Adapter 中主要改动的是 getItemViewTypeonCreateViewHolderonBindViewHolder 这三个方法,因此我们从此入手进行优化。

既然可能包含多个 type 类型的视图,那么能否将诸如 Banner、广告、文本、视频、新闻等视为一个 HeaderView 来统一处理呢?

getItemViewType 方法中:减少 if 等逻辑判断,简化代码,可以直接使用 hashCode 作为 type 标识。通过创建列表布局类型,返回的不再是简单的布局类型标识,而是布局对应的 hashCode 值。

private ArrayList headers = new ArrayList<>();
public interface InterItemView {
    /** 创建view */
    View onCreateView(ViewGroup parent);
    /** 绑定view */
    void onBindView(View headerView);
}

@Deprecated
@Override
public final int getItemViewType(int position) {
    if (headers.size()!=0){
        if (position= 0){
            return footers.get(i).hashCode();
        }
    }
    return getViewType(position-headers.size());
}

onCreateViewHolder 方法中:getItemViewType 返回的是布局的 hashCode 值,即 onCreateViewHolder(ViewGroup parent, int viewType) 参数中的 viewType

@NonNull
@Override
public final BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = createViewByType(parent, viewType);
    if (view!=null){
        return new BaseViewHolder(view);
    }
    final BaseViewHolder viewHolder = OnCreateViewHolder(parent, viewType);
    setOnClickListener(viewHolder);
    return viewHolder;
}

private View createViewByType(ViewGroup parent, int viewType){
    for (InterItemView headerView : headers){
        if (headerView.hashCode() == viewType){
            View view = headerView.onCreateView(parent);
            StaggeredGridLayoutManager.LayoutParams layoutParams;
            if (view.getLayoutParams()!=null) {
                layoutParams = new StaggeredGridLayoutManager.LayoutParams(view.getLayoutParams());
            } else {
                layoutParams = new StaggeredGridLayoutManager.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            }
            layoutParams.setFullSpan(true);
            view.setLayoutParams(layoutParams);
            return view;
        }
    }
    for (InterItemView footerView : footers){
        if (footerView.hashCode() == viewType){
            View view = footerView.onCreateView(parent);
            StaggeredGridLayoutManager.LayoutParams layoutParams;
            if (view.getLayoutParams()!=null) {
                layoutParams = new StaggeredGridLayoutManager.LayoutParams(view.getLayoutParams());
            } else {
                layoutParams = new StaggeredGridLayoutManager.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            }
            layoutParams.setFullSpan(true);
            view.setLayoutParams(layoutParams);
            return view;
        }
    }
    return null;
}

onBindViewHolder 方法中:可以看到,添加 header 类型视图后,通过 onBindView 进行数据绑定即可。

@Override
public final void onBindViewHolder(BaseViewHolder holder, int position) {
    holder.itemView.setId(position);
    if (headers.size()!=0 && position=0){
        footers.get(i).onBindView(holder.itemView);
        return ;
    }
    OnBindViewHolder(holder,position-headers.size());
}

如何使用?下面以 Banner 类型为例,展示了如何解耦之前 Adapter 中的复杂操作:

InterItemView interItemView = new InterItemView() {
    @Override
    public View onCreateView(ViewGroup parent) {
        BannerView header = new BannerView(HeaderFooterActivity.this);
        header.setHintView(new ColorPointHintView(HeaderFooterActivity.this,Color.YELLOW, Color.GRAY));
        header.setHintPadding(0, 0, 0, (int) AppUtils.convertDpToPixel(8, HeaderFooterActivity.this));
        header.setPlayDelay(2000);
        header.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
            (int) AppUtils.convertDpToPixel(200, HeaderFooterActivity.this)));
        header.setAdapter(new BannerAdapter(HeaderFooterActivity.this));
        return header;
    }
    @Override
    public void onBindView(View headerView) {}
};
adapter.addHeader(interItemView);

封装后的优势十分明显:

  • 拓展性——Adapter 无需关心不同列表类型在列表中的具体位置,因此可以随意增加或减少列表类型,操作便捷。同时,类型视图的布局创建与数据绑定都不需要在 Adapter 中处理,实现了充分解耦。
  • 可维护性——不同的列表类型通过 Adapter 添加 headerView 来处理,即使添加多个 headerView,彼此之间互不影响,代码简洁,维护成本低。
来源:https://developer.aliyun.com/article/704533
上一篇WP NssUser Register插件最新严重权限提升漏洞CVE-2024-54363利用工具下载地址使用方法 下一篇千问云上线Qwen3.7-Max 支持API与Token Plan调用
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Synthesia零基础教程:客户端安装与工作区权限设置
AI教程 · 2026-06-07

Synthesia零基础教程:客户端安装与工作区权限设置

本文介绍了AI视频生成工具Synthesia的入门流程。内容涵盖从官网下载客户端、完成账户注册与登录,到软件安装与启动的完整步骤。详细说明了如何初始化工作区,包括创建首个AI视频项目、选择模板与AI主播。最后,指导用户理解并设置团队协作中的不同权限角色,以便安全高效地共同管理项目。

FramePack新手入门指南:安装启动报错修复导出全流程
AI教程 · 2026-06-07

FramePack新手入门指南:安装启动报错修复导出全流程

本文详细介绍了FramePack工具从下载安装到项目导出的完整流程。内容涵盖软件安装步骤、首次启动设置、常见报错解决方案以及项目打包导出方法。指南旨在帮助用户快速掌握工具核心操作,解决使用过程中可能遇到的技术问题,确保顺利完成AI视频帧处理任务。

FLUX.1保姆级教程:环境安装、显存优化与首次出图测试
AI教程 · 2026-06-07

FLUX.1保姆级教程:环境安装、显存优化与首次出图测试

本文详细介绍了FLUX 1的安装与初步使用流程。内容涵盖从Python环境配置、代码仓库克隆、依赖包安装,到关键的显存优化设置,最后指导用户完成首次文生图测试。教程旨在帮助用户顺利搭建运行环境,解决常见安装问题,并实现基础图像生成功能。

AnythingLLM新手实战:本地大模型部署后知识库接入设置
AI教程 · 2026-06-07

AnythingLLM新手实战:本地大模型部署后知识库接入设置

本文介绍了在本地部署大模型后,如何为AnythingLLM设置知识库。内容涵盖知识库的基本概念、创建与配置步骤、文档上传与处理技巧,以及如何通过问答测试其效果。旨在帮助用户有效整合本地文档资源,构建个性化的AI知识助手,提升信息检索与利用效率。

Aider安装失败排查:扩展冲突与登录异常全解析
AI教程 · 2026-06-07

Aider安装失败排查:扩展冲突与登录异常全解析

本文针对Aider安装过程中常见的扩展冲突与登录异常问题,提供了系统的排查思路与解决方案。内容涵盖如何识别并处理与其他AI工具的兼容性问题,解决因网络或账户设置导致的登录失败,以及通过环境检查、依赖更新等步骤彻底排除安装障碍,帮助用户顺利完成安装与配置。