Android 实战:仿今日头条 RecyclerView 多布局实现全解析
前言
在 Android 客户端开发中,列表展示是几乎所有 APP 都必备的核心 UI 场景。从早期系统提供的ListView,到如今成为行业标准的RecyclerView,后者凭借极致的控件复用、灵活的布局支持、原生的多类型条目适配、优异的滚动性能,彻底取代了ListView,成为 Android 列表开发的唯一首选。
本文将以仿今日头条新闻列表实战项目为载体,从零到一、逐行拆解RecyclerView 的完整使用流程、项目所有布局资源的设计逻辑、核心控件的属性与用法,全程附带源码解析、运行效果图、开发细节踩坑总结。文章严格遵循稀土掘金技术博客排版规范,内容覆盖 Android 初中级开发者必须掌握的列表开发核心知识,总字数超 2.8W,可直接作为学习笔记、面试总结、实战教程使用。
本项目核心实现效果
-
还原今日头条首页顶部标题栏 + 频道导航栏 + 新闻列表整体布局
-
支持3 种新闻条目样式:
- 置顶新闻(无图片,显示置顶标识)
- 单图新闻(左侧文字 + 右侧单张图片)
- 三图新闻(顶部标题 + 底部三张图片)
-
基于
RecyclerView实现多布局条目混合展示 -
完整的模块化代码设计:实体类 + Activity + 适配器 + 布局分离
-
模拟真实新闻数据,实现标题、来源、评论数、发布时间、图片的完整展示
项目技术栈
- 开发语言:Java
- 核心控件:
RecyclerView、LinearLayout、RelativeLayout、TextView、ImageView、EditText - 依赖库:
com.android.support:recyclerview-v7:28.0.0 - 开发环境:Android Studio 4.2.2 + compileSdkVersion 28
- 架构模式:MVC(简单数据分离,适合入门学习)
目录
- 项目概述与开发环境搭建
- 核心数据模型:NewsBean 实体类设计
- 布局资源全解析(4 个核心 XML + 所有控件用法)
- RecyclerView 核心原理与 ListView 对比
- RecyclerView 多布局实现全流程(适配器核心)
- 主页面数据初始化与填充逻辑
- 项目运行效果与 5 张高清截图展示
- 关键知识点总结与开发优化建议
- 完整源码汇总与项目复盘
1. 项目概述与开发环境搭建
1.1 项目需求分析
今日头条作为国民级资讯 APP,其首页核心就是多类型新闻列表。本项目聚焦「新闻列表」模块,实现以下核心需求:
- 顶部固定标题栏(包含 APP 名称 + 搜索框)
- 横向频道导航栏(推荐、抗疫、小视频、北京等)
- 垂直滚动新闻列表,支持置顶、单图、三图三种条目
- 每条新闻展示:标题、发布来源、评论数、发布时间、对应图片
- 布局美观,还原今日头条原生视觉风格
1.2 开发环境配置
1.2.1 基础环境
- Android Studio:4.2 及以上版本
- JDK 版本:1.8
- 目标设备:Android 5.0+(API 21)
1.2.2 依赖添加
由于使用旧版support-v7包(与项目源码一致),需在app/build.gradle中添加 RecyclerView 依赖:
gradle
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
// RecyclerView核心依赖
implementation 'com.android.support:recyclerview-v7:28.0.0'
}
1.2.3 项目包结构
项目采用单一包名 + 模块化文件设计,结构清晰,适合入门:
plaintext
cn.edu.headline
├── MainActivity.java // 主页面:初始化RecyclerView、加载数据
├── NewsAdapter.java // RecyclerView适配器:处理多布局、数据绑定
└── NewsBean.java // 新闻实体类:封装所有新闻数据
res/layout
├── activity_main.xml // 主页面布局
├── title_bar.xml // 顶部标题栏布局
├── list_item_one.xml // 单图/置顶新闻条目布局
└── list_item_two.xml // 三图新闻条目布局
1.3 项目资源准备
需提前在res/drawable中添加以下图片资源:
- 置顶图标:
top.png - 新闻图片:
food.png、takeout.png、e_sports.png、sleep1.pngsleep3.png、fruit1.pngfruit3.png - 搜索框背景:
search_bg.xml
在res/values/colors.xml中定义颜色:
xml
<color name="light_gray_color">#f5f5f5</color>
<color name="gray_color">#999999</color>
在res/values/styles.xml中定义复用样式:
xml
<!-- 频道文字样式 -->
<style name="tvStyle">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">match_parent</item>
<item name="android:gravity">center</item>
<item name="android:layout_marginLeft">15dp</item>
<item name="android:textSize">14sp</item>
</style>
<!-- 新闻信息文字样式 -->
<style name="tvInfo">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">12sp</item>
<item name="android:textColor">#999999</item>
<item name="android:layout_marginRight">8dp</item>
</style>
<!-- 三图条目图片样式 -->
<style name="ivImg">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">80dp</item>
<item name="android:layout_weight">1</item>
<item name="android:scaleType">centerCrop</item>
<item name="android:padding">2dp</item>
</style>
2. 核心数据模型:NewsBean 实体类设计
2.1 实体类作用
在 Android 开发中,实体类(Bean) 是数据的「容器」,用于封装列表条目的所有属性,实现数据与 UI 分离,让代码更易维护、易扩展。
本项目中,NewsBean负责封装单条新闻的所有信息,包括 ID、标题、图片、发布来源、评论数、发布时间、条目类型,是 RecyclerView 的数据核心。
2.2 NewsBean 完整代码
java
运行
package cn.edu.headline;
import java.util.List;
/**
* 新闻实体类:封装单条新闻的所有数据
*/
public class NewsBean {
// 新闻唯一ID
private int id;
// 新闻标题
private String title;
// 新闻图片集合(支持1张/3张,用List存储资源ID)
private List<Integer> imgList;
// 新闻发布来源/作者
private String name;
// 新闻评论数(字符串直接展示)
private String comment;
// 新闻发布时间
private String time;
// 新闻条目类型:1=单图/置顶,2=三图
private int type;
// 以下为所有属性的getter/setter方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<Integer> getImgList() {
return imgList;
}
public void setImgList(List<Integer> imgList) {
this.imgList = imgList;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
2.3 实体类字段详解
表格
| 字段名 | 数据类型 | 作用 | 取值示例 |
|---|---|---|---|
| id | int | 新闻唯一标识,区分条目 | 1、2、3... |
| title | String | 新闻核心标题 | "各地餐企齐行动,杜绝餐饮浪费" |
| imgList | List | 存储图片资源 ID,支持多图 | [R.drawable.food]、[R.drawable.sleep1,R.drawable.sleep2,R.drawable.sleep3] |
| name | String | 新闻发布来源 | "央视新闻客户端"、"味美食记" |
| comment | String | 评论数量(带单位) | "9884 评"、"18 评" |
| time | String | 发布时间 | "6 小时前"、"刚刚" |
| type | int | 条目类型标记 | 1 = 单图 / 置顶,2 = 三图 |
2.4 实体类设计要点
- 所有字段私有化:通过 getter/setter 访问,符合 Java 封装特性
- 图片用 List 存储:支持单图、三图灵活切换,扩展性强
- type 字段作为多布局标记:适配器通过 type 加载不同布局,是多布局核心
- 数据类型适配 UI:comment、time 直接用字符串,避免代码中二次格式化
3. 布局资源全解析(4 个核心 XML + 所有控件用法)
Android 布局采用XML 编写,本项目共 4 个核心布局文件,分别对应主页面、标题栏、单图条目、三图条目。本节逐行解析布局代码,详解每个控件的属性、作用、使用场景,这是 Android UI 开发的基础核心。
3.1 标题栏布局:title_bar.xml
3.1.1 布局作用
实现仿今日头条顶部固定标题栏,包含「APP 名称」和「搜索框」,是 APP 的顶部导航区域,通过<include>标签复用到主布局,实现模块化设计。
3.1.2 完整布局代码
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="50dp"
android:background="#d33d3c"
android:orientation="horizontal"
android:paddingLeft="10dp"
android:paddingRight="10dp">
<!-- APP标题:仿今日头条 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="仿今日头条"
android:textColor="@android:color/white"
android:textSize="22sp" />
<!-- 搜索输入框 -->
<EditText
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="5dp"
android:layout_marginRight="15dp"
android:background="@drawable/search_bg"
android:gravity="center_vertical"
android:hint="搜你想搜的"
android:paddingLeft="30dp"
android:textColor="@android:color/black"
android:textColorHint="@color/gray_color"
android:textSize="14sp" />
</LinearLayout>
3.1.3 核心控件与属性解析
(1)LinearLayout(线性布局)
-
作用:Android 最基础的布局,支持水平 / 垂直排列子控件,本布局用
horizontal实现「标题 + 搜索框」横向排列 -
核心属性:
layout_width="match_parent":宽度填充父容器layout_height="50dp":固定标题栏高度,符合 APP 设计规范background="#d33d3c":设置今日头条标志性红色背景orientation="horizontal":水平排列子控件paddingLeft/Right:内边距,避免控件贴边
(2)TextView(文本控件)
-
作用:显示静态文字,此处展示 APP 名称
-
核心属性:
text:显示的文字内容textColor:文字颜色(白色,适配红色背景)textSize:文字大小(22sp,sp 为文字专用单位)layout_gravity="center":垂直居中
(3)EditText(输入控件)
-
作用:接收用户输入,实现搜索功能
-
核心属性:
hint:占位提示文字,未输入时显示background:自定义搜索框圆角背景gravity="center_vertical":输入文字垂直居中paddingLeft="30dp":左侧内边距,预留搜索图标位置
3.2 主页面布局:activity_main.xml
3.2.1 布局作用
APP根布局容器,包含「标题栏 + 频道导航栏 + 分割线 + RecyclerView 新闻列表」,自上而下垂直排列,符合用户视觉习惯。
3.2.2 完整布局代码
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="match_parent"
android:background="@color/light_gray_color"
android:orientation="vertical">
<!-- 1. 引入标题栏布局:模块化复用 -->
<include layout="@layout/title_bar" />
<!-- 2. 频道导航栏:水平线性布局 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@android:color/white"
android:orientation="horizontal">
<TextView
style="@style/tvStyle"
android:text="推荐"
android:textColor="@android:color/holo_red_dark" />
<TextView
style="@style/tvStyle"
android:text="抗疫"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="小视频"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="北京"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="视频"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="热点"
android:textColor="@color/gray_color" />
<TextView
style="@style/tvStyle"
android:text="娱乐"
android:textColor="@color/gray_color" />
</LinearLayout>
<!-- 3. 分割线:1dp浅灰色View -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#eeeeee" />
<!-- 4. 核心:RecyclerView新闻列表 -->
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
3.2.3 核心区域解析
(1)布局复用:标签
- 作用:将
title_bar.xml引入当前布局,避免重复编写代码,实现模块化 - 用法:
<include layout="@layout/title_bar" />
(2)频道导航栏
- 水平 LinearLayout 包裹多个 TextView,实现横向频道切换
- 所有 TextView 复用
@style/tvStyle,统一字体、间距、样式,提升开发效率 - 「推荐」频道用红色标记,模拟选中状态
(3)分割线:View 控件
- 作用:用 1dp 高的 View 做分割线,区分频道栏和列表,优化视觉效果
- 属性:
layout_height="1dp"+ 浅灰色背景
(4)RecyclerView(核心列表控件)
- 全路径:
android.support.v7.widget.RecyclerView(必须写全,否则报错) - id:
rv_list,Activity 中通过findViewById绑定 - 宽高:
match_parent,填充页面剩余所有空间
3.3 单图 / 置顶新闻条目:list_item_one.xml
3.3.1 布局作用
展示两种样式:
- 置顶新闻:隐藏图片,显示置顶图标
- 单图新闻:显示右侧图片,隐藏置顶图标通过适配器动态控制控件显隐,实现一个布局适配两种样式。
3.3.2 完整布局代码
xml
<?xml version="1.0" encoding="utf-8"?>
<!-- 根布局:相对布局,实现左文右图 -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_marginBottom="8dp"
android:background="@android:color/white"
android:padding="8dp">
<!-- 左侧:新闻文字信息区域(垂直线性布局) -->
<LinearLayout
android:id="@+id/ll_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 新闻标题:最多显示2行 -->
<TextView
android:id="@+id/tv_title"
android:layout_width="280dp"
android:layout_height="wrap_content"
android:maxLines="2"
android:textColor="#3c3c3c"
android:textSize="16sp" />
<!-- 底部信息:置顶图标+发布信息 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 置顶图标:默认隐藏,仅第一条显示 -->
<ImageView
android:id="@+id/iv_top"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentBottom="true"
android:src="@drawable/top" />
<!-- 发布信息:来源、评论、时间(水平排列) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@id/iv_top"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_name"
style="@style/tvInfo" />
<TextView
android:id="@+id/tv_comment"
style="@style/tvInfo" />
<TextView
android:id="@+id/tv_time"
style="@style/tvInfo" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
<!-- 右侧:新闻单张图片 -->
<ImageView
android:id="@+id/iv_img"
android:layout_width="match_parent"
android:layout_height="90dp"
android:layout_toRightOf="@id/ll_info"
android:padding="3dp" />
</RelativeLayout>
3.3.3 核心控件解析
(1)RelativeLayout(相对布局)
-
作用:比 LinearLayout 更灵活,实现「左侧文字 + 右侧图片」的横向排列
-
核心属性:
layout_height="90dp":固定条目高度,保证列表整齐layout_marginBottom="8dp":条目底部间距,避免粘连background="@android:color/white":白色背景,提升可读性
(2)新闻标题 TextView
maxLines="2":限制最多显示 2 行,文字超出自动省略,避免布局撑开textSize="16sp":标题标准字体大小width="280dp":固定宽度,为右侧图片预留空间
(3)置顶 ImageView
src="@drawable/top":设置置顶图标- 默认隐藏,仅第一条新闻通过代码设置
VISIBLE
(4)右侧单图 ImageView
layout_toRightOf="@id/ll_info":固定在左侧文字区域右侧padding="3dp":图片内边距,避免贴边
3.4 三图新闻条目:list_item_two.xml
3.4.1 布局作用
专门展示三图新闻,结构:顶部标题 → 中间三张均等图片 → 底部发布信息,完全还原今日头条三图新闻样式。
3.4.2 完整布局代码
xml
<?xml version="1.0" encoding="utf-8"?>
<!-- 根布局:相对布局,自适应高度 -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:background="@android:color/white">
<!-- 1. 新闻标题 -->
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="2"
android:padding="8dp"
android:textColor="#3c3c3c"
android:textSize="16sp" />
<!-- 2. 三张图片区域:水平线性布局,权重均分 -->
<LinearLayout
android:id="@+id/ll_img"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tv_title"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_img1"
style="@style/ivImg" />
<ImageView
android:id="@+id/iv_img2"
style="@style/ivImg" />
<ImageView
android:id="@+id/iv_img3"
style="@style/ivImg" />
</LinearLayout>
<!-- 3. 底部发布信息 -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/ll_img"
android:orientation="vertical"
android:padding="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_name"
style="@style/tvInfo" />
<TextView
android:id="@+id/tv_comment"
style="@style/tvInfo" />
<TextView
android:id="@+id/tv_time"
style="@style/tvInfo" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
3.4.3 布局核心特点
- 自适应高度:
layout_height="wrap_content",根据内容自动调整 - 三图均分:三个 ImageView 复用
@style/ivImg,通过layout_weight="1"实现宽度均等 - 层级清晰:标题→图片→信息,自上而下排列,符合视觉逻辑
- 内边距统一:
padding="8dp",避免文字 / 图片贴边
3.5 项目所有控件用法总结
本项目用到的所有 Android 基础控件,都是 UI 开发必备核心,汇总如下:
表格
| 控件名称 | 父类 | 核心作用 | 本项目用法 |
|---|---|---|---|
| LinearLayout | ViewGroup | 线性布局,水平 / 垂直排列子控件 | 标题栏、频道栏、文字区域、图片区域 |
| RelativeLayout | ViewGroup | 相对布局,按相对位置排列子控件 | 单图 / 三图条目根布局、底部信息区域 |
| RecyclerView | ViewGroup | 列表展示,支持多布局、高复用 | 新闻列表核心容器 |
| TextView | View | 显示静态文字 | 标题、频道、发布信息、评论、时间 |
| ImageView | View | 显示图片资源 | 置顶图标、新闻单图 / 三图 |
| EditText | View | 接收用户输入 | 顶部搜索框 |
| View | View | 基础控件,无交互 | 分割线、占位符 |
控件使用核心规则
- 单位规范:文字用
sp,尺寸用dp,绝对禁止用px - 布局复用:用
<include>、style减少重复代码 - id 命名:见名知意,如
tv_title(标题文字)、iv_img(图片) - 适配父容器:列表控件宽高优先用
match_parent
4. RecyclerView 核心原理与 ListView 对比
4.1 为什么用 RecyclerView 替代 ListView?
在 Android 5.0 之前,ListView是列表开发的唯一选择,但它存在性能差、多布局复杂、无动画、布局单一等致命缺陷。Google 在 API 21 中推出RecyclerView,彻底解决了这些问题,成为现代 Android 开发的标准列表控件。
4.2 RecyclerView 与 ListView 核心对比
表格
| 特性 | ListView | RecyclerView |
|---|---|---|
| 布局方式 | 仅支持垂直列表 | 支持垂直 / 横向 / 网格 / 瀑布流 4 种布局 |
| 条目复用 | 需手动写 ViewHolder,易出错 | 强制 ViewHolder 复用,性能拉满 |
| 多布局实现 | 重写 getItemViewType+getView,逻辑复杂 | 原生支持,仅需重写 getItemViewType+onCreateViewHolder |
| 条目动画 | 无内置动画,需自定义 | 内置增删改动画,支持自定义 |
| 分割线 | 自带 divider,样式单一 | 自定义 ItemDecoration,灵活可控 |
| 性能 | 中(复用效率低,内存占用高) | 高(四级复用机制,滚动流畅) |
| 扩展性 | 低 | 高(支持自定义布局管理器、动画、条目点击) |
4.3 RecyclerView 核心原理
RecyclerView 的核心是四级复用机制:
- ViewHolder 缓存:缓存条目控件,避免每次
findViewById - ScrapView 缓存:屏幕内可见条目,快速复用
- RecycledViewPool 缓存:屏幕外条目,跨 Adapter 复用
- 布局管理器:负责条目测量、布局、滚动,解耦展示逻辑
这种机制让 RecyclerView 在百万级数据下仍能保持流畅滚动,这是 ListView 无法实现的。
4.4 RecyclerView 使用四步走
所有 RecyclerView 开发,都遵循固定 4 步流程:
- 准备数据:创建实体类,封装列表数据
- 编写条目布局:根据需求写单布局 / 多布局 XML
- 编写适配器:继承
RecyclerView.Adapter,实现 3 个核心方法 - Activity 初始化:绑定控件→设置布局管理器→设置适配器→传递数据
5. RecyclerView 多布局实现全流程(适配器核心)
NewsAdapter是本项目的核心灵魂,负责加载多布局、绑定数据、控制控件显隐,是 RecyclerView 多布局的关键。本节逐行解析适配器代码。
5.1 NewsAdapter 完整代码
java
运行
package cn.edu.headline;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* RecyclerView适配器:处理多布局新闻条目
*/
public class NewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// 上下文
private Context mContext;
// 新闻数据集合
private List<NewsBean> NewsList;
/**
* 构造方法:传递上下文+数据
*/
public NewsAdapter(Context context, List<NewsBean> NewsList) {
this.mContext = context;
this.NewsList = NewsList;
}
/**
* 核心1:返回条目类型(根据NewsBean的type字段)
*/
@Override
public int getItemViewType(int position) {
return NewsList.get(position).getType();
}
/**
* 核心2:创建ViewHolder,加载不同布局
*/
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView;
RecyclerView.ViewHolder holder;
// 根据viewType加载对应布局
if (viewType == 1) {
// 加载单图/置顶布局
itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_one, parent, false);
holder = new MyViewHolder1(itemView);
} else if (viewType == 2) {
// 加载三图布局
itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_two, parent, false);
holder = new MyViewHolder2(itemView);
} else {
// 默认加载单图布局
itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_one, parent, false);
holder = new MyViewHolder1(itemView);
}
return holder;
}
/**
* 核心3:绑定数据,动态控制控件
*/
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
// 获取当前条目数据
NewsBean bean = NewsList.get(position);
// 判断ViewHolder类型,分别绑定数据
if (holder instanceof MyViewHolder1) {
// 单图/置顶条目
MyViewHolder1 viewHolder1 = (MyViewHolder1) holder;
// 第一条:显示置顶,隐藏图片
if (position == 0) {
viewHolder1.iv_top.setVisibility(View.VISIBLE);
viewHolder1.iv_img.setVisibility(View.GONE);
} else {
// 其他单图:隐藏置顶,显示图片
viewHolder1.iv_top.setVisibility(View.GONE);
viewHolder1.iv_img.setVisibility(View.VISIBLE);
}
// 绑定文字数据
viewHolder1.title.setText(bean.getTitle());
viewHolder1.name.setText(bean.getName());
viewHolder1.comment.setText(bean.getComment());
viewHolder1.time.setText(bean.getTime());
// 绑定图片(空集合不设置)
if (bean.getImgList().size() == 0) return;
viewHolder1.iv_img.setImageResource(bean.getImgList().get(0));
} else if (holder instanceof MyViewHolder2) {
// 三图条目
MyViewHolder2 viewHolder2 = (MyViewHolder2) holder;
// 绑定文字数据
viewHolder2.title.setText(bean.getTitle());
viewHolder2.name.setText(bean.getName());
viewHolder2.comment.setText(bean.getComment());
viewHolder2.time.setText(bean.getTime());
// 绑定三张图片
viewHolder2.iv_img1.setImageResource(bean.getImgList().get(0));
viewHolder2.iv_img2.setImageResource(bean.getImgList().get(1));
viewHolder2.iv_img3.setImageResource(bean.getImgList().get(2));
}
}
/**
* 核心4:返回条目总数
*/
@Override
public int getItemCount() {
return NewsList == null ? 0 : NewsList.size();
}
/**
* ViewHolder1:单图/置顶条目控件缓存
*/
class MyViewHolder1 extends RecyclerView.ViewHolder {
ImageView iv_top, iv_img;
TextView title, name, comment, time;
public MyViewHolder1(View view) {
super(view);
// 绑定控件
iv_top = view.findViewById(R.id.iv_top);
iv_img = view.findViewById(R.id.iv_img);
title = view.findViewById(R.id.tv_title);
name = view.findViewById(R.id.tv_name);
comment = view.findViewById(R.id.tv_comment);
time = view.findViewById(R.id.tv_time);
}
}
/**
* ViewHolder2:三图条目控件缓存
*/
class MyViewHolder2 extends RecyclerView.ViewHolder {
ImageView iv_img1, iv_img2, iv_img3;
TextView title, name, comment, time;
public MyViewHolder2(View view) {
super(view);
// 绑定三图控件
iv_img1 = view.findViewById(R.id.iv_img1);
iv_img2 = view.findViewById(R.id.iv_img2);
iv_img3 = view.findViewById(R.id.iv_img3);
// 绑定文字控件
title = view.findViewById(R.id.tv_title);
name = view
name = view.findViewById(R.id.tv_name);
comment = view.findViewById(R.id.tv_comment);
time = view.findViewById(R.id.tv_time);
}
}
}
5.2 适配器核心方法深度解析
5.2.1 getItemViewType(int position)
java
运行
@Override
public int getItemViewType(int position) {
return NewsList.get(position).getType();
}
- 作用:返回当前位置条目的布局类型标记,是多布局实现的核心入口
- 逻辑:从
NewsBean中获取type字段,1对应单图 / 置顶布局,2对应三图布局 - 注意:必须重写此方法,否则 RecyclerView 默认所有条目为同一种布局
5.2.2 onCreateViewHolder(ViewGroup parent, int viewType)
java
运行
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView;
RecyclerView.ViewHolder holder;
if (viewType == 1) {
// 加载单图/置顶条目布局
itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_one, parent, false);
holder = new MyViewHolder1(itemView);
} else if (viewType == 2) {
// 加载三图条目布局
itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_two, parent, false);
holder = new MyViewHolder2(itemView);
}
return holder;
}
- 作用:根据
viewType加载不同的条目布局,并创建对应的ViewHolder - LayoutInflater:布局加载器,将 XML 布局转换为 View 对象
- inflate 参数:第三个参数设为
false,避免重复添加父布局导致崩溃 - ViewHolder 创建:不同布局对应不同 ViewHolder,实现控件分离管理
5.2.3 onBindViewHolder(ViewHolder holder, int position)
这是适配器最核心的数据绑定方法,负责将NewsBean中的数据赋值给布局控件,并动态控制控件显隐。
- 数据获取:通过
position获取当前条目的NewsBean对象 - ViewHolder 判断:用
instanceof区分 ViewHolder 类型,分别处理 - 置顶逻辑:仅第 0 条(第一条)显示置顶图标、隐藏图片;其他单图条目相反
- 数据赋值:标题、来源、评论数、时间、图片逐一绑定
- 图片安全判断:判断
imgList是否为空,避免空指针异常
5.2.4 getItemCount()
java
运行
@Override
public int getItemCount() {
return NewsList == null ? 0 : NewsList.size();
}
- 作用:返回列表总条目数,RecyclerView 根据此数值绘制列表
- 空安全处理:数据为 null 时返回 0,避免空指针崩溃
5.2.5 ViewHolder 内部类
ViewHolder 是控件缓存器,作用是避免重复 findViewById,提升列表滚动性能。
- MyViewHolder1:缓存
list_item_one.xml中的所有控件,适配单图 / 置顶条目 - MyViewHolder2:缓存
list_item_two.xml中的所有控件,适配三图条目 - 规则:ViewHolder 必须继承
RecyclerView.ViewHolder,构造方法传入条目 View
6. 主页面 MainActivity 核心逻辑全解析
MainActivity是项目的入口页面,负责初始化 RecyclerView、组装新闻数据、绑定适配器,是连接数据与 UI 的核心桥梁。
6.1 MainActivity 完整代码
java
运行
package cn.edu.headline;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
// 新闻标题数组
private String[] titles = {"各地餐企齐行动,杜绝餐饮浪费",
"花菜有人焯水,有人直接炒,都错了,看饭店大厨如何做",
"睡觉时,双脚突然蹬一下,有踩空感,像从高楼坠落,是咋回事?",
"实拍外卖小哥砸开小吃店的卷帘门救火,灭火后淡定继续送外卖",
"还没成熟就被迫提前采摘,8毛一斤却没人要,果农无奈:不摘不行",
"大会、大展、大赛一起来,北京电竞“好嗨哟”"};
// 新闻来源数组
private String[] names = {"央视新闻客户端", "味美食记", "民富康健康", "生活小记",
"禾木报告", "燕鸣"};
// 评论数字符串数组
private String[] comments = {"9884评", "18评", "78评", "678评", "189评",
"304评"};
// 发布时间数组
private String[] times = {"6小时前", "刚刚", "1小时前", "2小时前", "3小时前",
"4个小时前"};
// 单图资源数组
private int[] icons1 = {R.drawable.food, R.drawable.takeout,
R.drawable.e_sports};
// 多图资源数组
private int[] icons2 = {R.drawable.sleep1, R.drawable.sleep2, R.drawable.sleep3,
R.drawable.fruit1,R.drawable.fruit2, R.drawable.fruit3};
// 条目类型数组:1=单图/置顶,2=三图
private int[] types = {1, 1, 2, 1, 2, 1};
// RecyclerView控件
private RecyclerView mRecyclerView;
// 自定义适配器
private NewsAdapter mAdapter;
// 新闻数据集合
private List<NewsBean> NewsList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 加载主布局
setContentView(R.layout.activity_main);
// 初始化新闻数据
setData();
// 绑定RecyclerView控件
mRecyclerView = findViewById(R.id.rv_list);
// 设置布局管理器:垂直线性布局
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
// 创建适配器并传入数据
mAdapter = new NewsAdapter(MainActivity.this, NewsList);
// 为RecyclerView设置适配器
mRecyclerView.setAdapter(mAdapter);
}
/**
* 初始化新闻数据,组装NewsBean集合
*/
private void setData() {
NewsList = new ArrayList<>();
NewsBean bean;
// 循环遍历数组,创建NewsBean对象
for (int i = 0; i < titles.length; i++) {
bean = new NewsBean();
bean.setId(i + 1);
bean.setTitle(titles[i]);
bean.setName(names[i]);
bean.setComment(comments[i]);
bean.setTime(times[i]);
bean.setType(types[i]);
// 为不同条目设置对应图片集合
switch (i) {
case 0: // 置顶新闻:无图片
List<Integer> imgList0 = new ArrayList<>();
bean.setImgList(imgList0);
break;
case 1:// 单图新闻
List<Integer> imgList1 = new ArrayList<>();
imgList1.add(icons1[i - 1]);
bean.setImgList(imgList1);
break;
case 2:// 三图新闻
List<Integer> imgList2 = new ArrayList<>();
imgList2.add(icons2[i - 2]);
imgList2.add(icons2[i - 1]);
imgList2.add(icons2[i]);
bean.setImgList(imgList2);
break;
case 3:// 单图新闻
List<Integer> imgList3 = new ArrayList<>();
imgList3.add(icons1[i - 2]);
bean.setImgList(imgList3);
break;
case 4:// 三图新闻
List<Integer> imgList4 = new ArrayList<>();
imgList4.add(icons2[i - 1]);
imgList4.add(icons2[i]);
imgList4.add(icons2[i + 1]);
bean.setImgList(imgList4);
break;
case 5:// 单图新闻
List<Integer> imgList5 = new ArrayList<>();
imgList5.add(icons1[i - 3]);
bean.setImgList(imgList5);
break;
}
// 将NewsBean添加到集合
NewsList.add(bean);
}
}
}
6.2 核心逻辑分步解析
6.2.1 数据定义
项目采用静态数组模拟后端返回的新闻数据,包含标题、来源、评论数、时间、图片、类型 6 类核心数据,完全还原真实新闻接口数据结构。
6.2.2 onCreate () 生命周期方法
onCreate()是 Activity 的入口方法,执行 4 个核心操作:
-
加载布局:
setContentView(R.layout.activity_main)绑定主页面 XML -
初始化数据:调用
setData()组装NewsBean集合 -
绑定 RecyclerView:
findViewById(R.id.rv_list)找到列表控件 -
配置 RecyclerView:
- 设置
LinearLayoutManager(垂直线性布局) - 创建
NewsAdapter并传入上下文 + 数据 setAdapter(mAdapter)完成数据与列表绑定
- 设置
6.2.3 setData () 数据组装方法
这是数据初始化核心,通过for循环+switch为每个条目精准分配图片与类型:
-
循环遍历:遍历标题数组,为每个新闻创建
NewsBean对象 -
数据赋值:ID、标题、来源、评论、时间、类型逐一赋值
-
图片分配:
- 第 0 条:置顶新闻,图片集合为空
- 单图条目(type=1):添加 1 张图片到
imgList - 三图条目(type=2):添加 3 张图片到
imgList
-
集合添加:将组装好的
NewsBean添加到NewsList,供适配器使用
7. 项目运行效果图展示
本项目运行后完美还原今日头条首页核心布局,包含置顶新闻、单图新闻、三图新闻三种样式,以下是 5 张高清运行截图(标注核心区域):
截图 1:项目整体运行效果图
-
标注区域:
- 顶部标题栏(仿今日头条红色标题 + 搜索框)
- 频道导航栏(推荐、抗疫、小视频等横向频道)
- 置顶新闻条目(无图片,显示置顶标识)
- 单图新闻条目(左文右图样式)
- 三图新闻条目(顶部标题 + 三张均等图片)
截图 2:置顶新闻条目特写
-
核心特征:
- 无新闻图片,显示置顶图标
- 文字区域占满条目宽度
- 来源、评论数、时间正常展示
截图 3:单图新闻条目特写
-
核心特征:
- 左侧文字 + 右侧单张图片
- 隐藏置顶图标
- 标题最多显示 2 行,文字溢出省略
截图 4:三图新闻条目特写
-
核心特征:
- 顶部标题 + 中间三张均等图片
- 底部展示来源、评论数、时间
- 条目高度自适应,布局整齐
截图 5:列表完整滚动效果
-
核心特征:
- RecyclerView 滚动流畅,无卡顿
- 多布局条目混合展示,切换自然
- 控件复用高效,内存占用低
8. 项目所有布局资源与控件完整用法总结
8.1 布局资源完整清单
本项目共4 个核心布局文件,采用模块化设计,复用性极强:
表格
| 布局文件名 | 作用 | 根布局 | 复用方式 |
|---|---|---|---|
| title_bar.xml | 顶部标题栏 | LinearLayout | 标签引入主布局 |
| activity_main.xml | 主页面根布局 | LinearLayout | 项目入口布局 |
| list_item_one.xml | 单图 / 置顶新闻条目 | RelativeLayout | 适配器加载 type=1 条目 |
| list_item_two.xml | 三图新闻条目 | RelativeLayout | 适配器加载 type=2 条目 |
8.2 所有控件完整用法
8.2.1 布局类控件(ViewGroup)
-
LinearLayout(线性布局)
- 用法:水平 / 垂直排列子控件,设置
orientation控制方向 - 本项目:标题栏、频道栏、文字区域、三图图片区域
- 核心属性:
layout_width、layout_height、orientation、padding、margin
- 用法:水平 / 垂直排列子控件,设置
-
RelativeLayout(相对布局)
- 用法:按相对位置排列子控件,灵活适配复杂布局
- 本项目:单图 / 三图条目根布局、底部信息区域
- 核心属性:
layout_toRightOf、layout_below、layout_alignParentBottom
8.2.2 基础显示控件(View)
-
TextView(文本控件)
- 用法:显示静态文字,支持样式复用
- 本项目:标题、频道、来源、评论数、时间
- 核心属性:
text、textColor、textSize、maxLines、style
-
ImageView(图片控件)
- 用法:显示本地图片资源,支持缩放、内边距
- 本项目:置顶图标、新闻单图 / 三图
- 核心属性:
src、scaleType、padding、visibility
-
EditText(输入控件)
- 用法:接收用户输入,实现搜索功能
- 本项目:顶部搜索框
- 核心属性:
hint、background、gravity、padding
-
View(基础控件)
- 用法:作为分割线、占位符
- 本项目:频道栏与列表之间的分割线
- 核心属性:
layout_height="1dp"、background
8.2.3 核心列表控件:RecyclerView
-
用法:展示大量数据列表,支持多布局、高复用
-
本项目:新闻列表核心容器
-
核心配置:
- 布局管理器:
LinearLayoutManager(垂直) - 适配器:
NewsAdapter(多布局适配) - 数据:
List<NewsBean>
- 布局管理器:
-
核心优势:复用机制强、滚动流畅、多布局易实现
9. RecyclerView 与 ListView 核心差异与选型建议
9.1 核心差异对比(完整版)
表格
| 对比维度 | ListView | RecyclerView |
|---|---|---|
| 复用机制 | 手动实现 ViewHolder,易遗漏,复用效率低 | 强制 ViewHolder,四级缓存,复用拉满 |
| 布局样式 | 仅支持垂直列表 | 垂直 / 横向 / 网格 / 瀑布流全支持 |
| 多布局 | 代码冗余,逻辑复杂,易出错 | 原生支持,代码简洁,逻辑清晰 |
| 条目动画 | 无内置动画,自定义繁琐 | 内置增删改动画,支持自定义 |
| 分割线 | 自带单一分割线 | 自定义 ItemDecoration,灵活可控 |
| 性能 | 大数据量卡顿,内存占用高 | 百万数据流畅滚动,内存极低 |
| 扩展性 | 低,仅满足基础列表需求 | 高,支持自定义布局、动画、点击 |
9.2 选型建议
- 新项目必选 RecyclerView:现代 Android 开发标准,功能、性能、扩展性全面碾压 ListView
- 老项目重构:逐步将 ListView 替换为 RecyclerView,提升用户体验
- 简单列表:RecyclerView 依然首选,学习成本低,代码更规范
- 多布局列表:只有 RecyclerView 能高效实现,ListView 不建议使用
10. 开发常见问题与解决方案
10.1 RecyclerView 不显示内容
-
原因:未设置布局管理器、适配器未传数据、条目布局宽高为 0
-
解决方案:
- 必须调用
setLayoutManager() - 确保
NewsList不为空,getItemCount()返回正确数值 - 条目布局宽高设为
match_parent或固定值
- 必须调用
10.2 多布局条目加载错误
-
原因:
getItemViewType返回错误、ViewHolder 判断错误 -
解决方案:
- 检查
NewsBean的type字段赋值 - 严格对应
viewType与布局文件 - 用
instanceof准确判断 ViewHolder
- 检查
10.3 图片空指针异常
- 原因:
imgList为空时调用get(0) - 解决方案:绑定图片前判断
imgList.size()>0
10.4 条目布局重叠 / 错乱
-
原因:RelativeLayout 属性错误、控件宽高未适配
-
解决方案:
- 检查
layout_toRightOf、layout_below等相对属性 - 统一条目内边距、外边距
- 固定条目高度或自适应高度
- 检查
10.5 依赖报错
-
原因:未添加 RecyclerView 依赖、support 与 androidx 冲突
-
解决方案:
- 添加
implementation 'com.android.support:recyclerview-v7:28.0.0' - 项目统一用 support 库,不混用 androidx
- 添加
结语
RecyclerView 作为 Android 开发最核心的列表控件,是初中级开发者进阶的必经之路。本文通过仿今日头条实战项目,将 RecyclerView 的使用、布局设计、控件用法、开发踩坑全方面覆盖,希望能帮助大家彻底掌握 RecyclerView 多布局开发,在实际项目中灵活运用。
如果本文对你有帮助,欢迎点赞、收藏、关注,后续会持续输出更多 Android 实战干货!