HeadLine仿今日头条项目分析

0 阅读27分钟

前言

在 Android 开发中,列表展示是最核心、最常用的 UI 场景之一。从早期的 ListView 到如今的 RecyclerView,列表控件的迭代不仅带来了性能的飞跃,更让复杂多样的列表布局实现变得更加灵活。

本文将以《Android 移动开发基础案例教程(第 3 版)》中的仿今日头条推荐列表项目为蓝本,用 2 万字的深度解析,带你彻底搞懂 RecyclerView 的核心原理、使用流程、布局设计、控件搭配,以及多类型列表的完整实现方案。无论你是刚入门的 Android 新手,还是想系统梳理 RecyclerView 知识的开发者,这篇博客都能帮你从 0 到 1 吃透这个核心控件。


一、项目背景与核心需求拆解

1.1 项目效果与目标

本项目最终要实现一个仿今日头条的推荐列表界面,核心效果如下:

  • 顶部有自定义标题栏,包含搜索框和导航标签

  • 列表支持3 种条目样式

    1. 无图 / 置顶样式(仅文字,无图片)
    2. 单图样式(标题 + 文字 + 1 张图片)
    3. 三图样式(标题 + 文字 + 3 张横向排列的图片)
  • 列表滚动流畅,条目复用高效,样式统一可维护

1.2 为什么选择 RecyclerView 而非 ListView

很多新手会疑惑:为什么不用 ListView 实现?这里先做一个核心对比,帮你理解技术选型的原因:

表格

特性ListViewRecyclerView
视图复用仅复用 convertView,逻辑需手动实现,易出错内置 ViewHolder 模式,强制复用,性能更优
布局管理器仅支持垂直列表,横向需自定义支持线性、网格、瀑布流,可自定义布局管理器
条目动画无原生支持,需手动实现内置 ItemAnimator,支持增删改查动画
多类型条目需手动在 getView 中判断类型,逻辑复杂原生支持 getItemViewType,多类型实现更优雅
性能优化无预取、局部刷新等优化支持预取、局部刷新、差分更新等高级优化

本项目需要实现多类型条目、高效滚动,因此 RecyclerView 是唯一的最优选择。

1.3 项目整体架构梳理

在开始代码解析前,我们先梳理整个项目的核心模块,让你对整体流程有清晰认知:

  1. 项目创建与依赖引入:创建工程,引入 androidx.recyclerview

  2. 样式与资源准备:定义文本样式、图片样式、颜色值,统一 UI 风格

  3. 布局文件设计

    • 主布局 activity_main.xml:承载标题栏、分割线、RecyclerView
    • 标题栏布局 title_bar.xml:复用的顶部导航栏
    • 单图条目布局 list_item_one.xml:单图 / 无图样式
    • 三图条目布局 list_item_two.xml:三图样式
  4. 数据实体类NewsBean.java,封装新闻数据

  5. 适配器实现NewsAdapter.java,核心的 RecyclerView 适配器,处理多类型条目

  6. 主 Activity 逻辑MainActivity.java,初始化数据、绑定 RecyclerView、设置布局管理器

  7. 样式与主题配置styles.xmlcolors.xmlAndroidManifest.xml,统一主题与样式


二、RecyclerView 核心基础:原理与核心组件

在进入项目代码前,我们必须先吃透 RecyclerView 的核心原理,否则只会 “抄代码” 而不懂原理,遇到问题就会无从下手。

2.1 RecyclerView 核心原理

RecyclerView 是一个 **“容器型控件” ,它本身不负责条目布局、条目样式,只负责复用视图、回收视图、滚动布局 **,所有的条目逻辑都交给适配器 Adapter 处理。

它的核心工作流程是:

  1. 测量布局:布局管理器 LayoutManager 负责计算每个条目的位置和大小
  2. 视图创建:适配器 onCreateViewHolder 创建条目视图,封装为 ViewHolder
  3. 数据绑定:适配器 onBindViewHolder 给条目视图绑定数据
  4. 视图复用:当条目滑出屏幕时,RecyclerView 回收 ViewHolder;新条目滑入时,复用回收的 ViewHolder,只重新绑定数据,避免重复创建视图,大幅提升性能
  5. 视图回收:系统自动回收不可见的条目,优化内存

2.2 RecyclerView 四大核心组件

2.2.1 RecyclerView 控件本身

RecyclerView 是列表的容器,在布局中声明后,需要:

  • 设置布局管理器(决定列表的排列方式)
  • 设置适配器(决定条目样式和数据绑定)
  • 可选设置条目动画分割线

2.2.2 LayoutManager(布局管理器)

LayoutManager 是 RecyclerView 的 “布局大脑”,负责:

  • 测量和摆放所有条目
  • 处理滚动逻辑
  • 决定条目复用的时机

Android 提供了 3 种原生布局管理器:

  1. LinearLayoutManager:线性布局,支持垂直 / 水平列表(本项目使用)
  2. GridLayoutManager:网格布局,支持多列列表
  3. StaggeredGridLayoutManager:瀑布流布局,支持不规则高度的多列列表

2.2.3 Adapter(适配器)

Adapter 是 RecyclerView 的 “数据桥梁”,是我们开发中最核心的部分,负责:

  • 创建 ViewHolder(onCreateViewHolder
  • 绑定数据到 ViewHolder(onBindViewHolder
  • 返回条目总数(getItemCount
  • 多类型条目时,返回条目类型(getItemViewType

2.2.4 ViewHolder(视图持有者)

ViewHolder 是条目的 “视图缓存容器”,它的核心作用是缓存条目内的所有控件,避免每次绑定数据时都调用 findViewById,大幅提升性能。

在本项目中,因为有两种条目样式,所以我们定义了两个 ViewHolder:MyViewViewHolder1(单图 / 无图)和 MyViewViewHolder2(三图)。


三、项目从零搭建:从创建到样式配置

3.1 项目创建与依赖引入

3.1.1 创建项目

按照教程要求,创建名为 HeadLine 的应用,包名 cn.itcast.headline,选择 Empty Activity 模板,语言选择 Java。

3.1.2 引入 RecyclerView 依赖

RecyclerView 属于 AndroidX 库,需要在 app/build.gradle(Module 级别)的 dependencies 中添加依赖:

gradle

dependencies {
    // 其他依赖...
    implementation 'androidx.recyclerview:recyclerview:1.3.2'
}

添加后同步 Gradle,即可在项目中使用 RecyclerView 控件。

3.1.3 导入界面图片

将项目需要的图片 food.pnge_sports.pngfruit1.pngfruit2.pngfruit3.pngsearch_bg.pngsleep1.pngsleep2.pngsleep3.pngtakeout.pngtop.png 导入到 res/drawable-hdpi 文件夹中,用于条目图片展示。

3.2 样式与颜色配置:统一 UI 风格

为了让列表样式统一、代码可维护,我们将重复的样式抽取到 styles.xml,颜色值抽取到 colors.xml,避免在布局中重复写相同属性。

3.2.1 定义文本样式(styles.xml)

res/values/styles.xml 中定义两种文本样式:tvStyle(导航栏标题样式)和 tvInfo(条目底部文字样式),代码如下:

xml

<!-- 导航栏文本样式 -->
<style name="tvStyle">
    <item name="android:layout_width">wrap_content</item>
    <item name="android:layout_height">match_parent</item>
    <item name="android:padding">10dp</item>
    <item name="android:gravity">center</item>
    <item name="android:textSize">15sp</item>
</style>

<!-- 条目底部文字样式 -->
<style name="tvInfo">
    <item name="android:layout_width">wrap_content</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:layout_marginLeft">8dp</item>
    <item name="android:layout_gravity">center_vertical</item>
    <item name="android:textSize">14sp</item>
    <item name="android:textColor">@color/gray_color</item>
</style>
  • tvStyle:用于顶部导航栏的 “推荐”“AI”“小视频” 等标签,设置居中、内边距、字号
  • tvInfo:用于条目底部的用户名、评论数、时间,设置左间距、垂直居中、深灰色文字

3.2.2 定义图片样式(styles.xml)

条目内的图片样式统一,因此定义 ivImg 样式,代码如下:

xml

<style name="ivImg">
    <item name="android:layout_width">0dp</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:layout_weight">1</item>
    <!-- ll_info 为布局文件 list_item_one.xml 中的 id -->
    <item name="android:layout_toRightOf">@id/ll_info</item>
</style>
  • layout_weight="1":在横向线性布局中平分宽度,实现 3 张图片等宽
  • layout_toRightOf:让图片在文字布局的右侧,实现左文右图的布局

3.2.3 定义颜色值(colors.xml)

res/values/colors.xml 中定义背景色和文字色,方便全局调用:

xml

<color name="light_gray_color">#eeeeee</color>
<color name="gray_color">#828282</color>
  • light_gray_color:列表背景浅灰色
  • gray_color:条目底部文字深灰色

3.2.4 删除默认标题栏

Android 默认的 ActionBar 样式不符合需求,需要在 AndroidManifest.xml 中修改主题:

xml

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.AppCompat.NoActionBarBar">
    <activity
        android:name=".MainActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

将主题改为 Theme.AppCompat.NoActionBar,即可隐藏默认标题栏,使用自定义标题栏。


四、布局文件全解析:从主布局到条目布局

布局是 RecyclerView 的 “骨架”,本项目包含 4 个核心布局文件,我们逐个拆解每个布局的作用、控件、属性和使用逻辑。

4.1 标题栏布局:title_bar.xml

4.1.1 布局作用

将顶部标题栏抽取为独立布局,通过 <include> 标签引入主布局,实现代码复用,避免主布局代码冗余。

4.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="wrap_content"
    android:background="@color/light_gray_color"
    android:orientation="horizontal">

    <!-- 仿今日头条文本 -->
    <TextView
        style="@style/tvStyle"
        android:text="仿今日头条"
        android:textColor="#000000"
        android:textSize="18sp" />

    <!-- 搜索输入框 -->
    <EditText
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_margin="5dp"
        android:background="@drawable/search_bg"
        android:hint="搜索"
        android:padding="5dp" />

    <!-- 导航标签:推荐 -->
    <TextView
        style="@style/tvStyle"
        android:text="推荐"
        android:textColor="#FF5722" />

    <!-- 导航标签:AI -->
    <TextView
        style="@style/tvStyle"
        android:text="AI"
        android:textColor="#000000" />

    <!-- 导航标签:小视频 -->
    <TextView
        style="@style/tvStyle"
        android:text="小视频"
        android:textColor="#000000" />

</LinearLayout>

4.1.3 核心控件与属性解析

  1. 根布局:LinearLayout(横向)

    • orientation="horizontal":横向排列子控件
    • background="@color/light_gray_color":设置浅灰色背景
    • layout_width="match_parent"layout_height="wrap_content":宽度铺满屏幕,高度自适应内容
  2. TextView(仿今日头条)

    • 引用 @style/tvStyle 统一样式
    • textSize="18sp":放大字号,突出标题
    • textColor="#000000":黑色文字
  3. EditText(搜索框)

    • layout_weight="1":在横向布局中占据剩余空间,实现宽度自适应
    • background="@drawable/search_bg":设置自定义搜索框背景
    • hint="搜索":占位提示文字
  4. 导航标签 TextView

    • 全部引用 @style/tvStyle,保证样式统一
    • “推荐” 标签设置橙色文字 #FF5722,突出当前选中状态

4.2 主布局:activity_main.xml

4.2.1 布局作用

作为 Activity 的主布局,承载标题栏、分割线、RecyclerView,是整个列表的根布局。

4.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">

    <!-- 引入自定义标题栏 -->
    <include layout="@layout/title_bar" />

    <!-- 灰色分割线 -->
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/gray_color" />

    <!-- RecyclerView 列表容器 -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/light_gray_color" />

</LinearLayout>

4.2.3 核心控件与属性解析

  1. 根布局:LinearLayout(垂直)

    • orientation="vertical":垂直排列子控件
    • background="@color/light_gray_color":全局浅灰色背景
  2. 标签

    • 引入 title_bar.xml 布局,实现标题栏复用,代码简洁易维护
  3. View(分割线)

    • 用一个高度 1dp 的 View 实现分割线,比使用 Drawable 更简单
    • background="@color/gray_color":深灰色,与条目文字颜色统一
  4. RecyclerView 控件

    • id="@+id/rv_list":在 Activity 中通过 findViewById 找到该控件
    • layout_width="match_parent"layout_height="match_parent":铺满剩余屏幕空间
    • 注意:必须使用完整类名 androidx.recyclerview.widget.RecyclerView,否则会报错

4.3 单图 / 无图条目布局:list_item_one.xml

4.3.1 布局作用

用于展示无图(置顶)单图两种条目样式,通过代码控制图片的显示 / 隐藏,实现一个布局适配两种样式。

4.3.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="wrap_content"
    android:layout_margin="5dp"
    android:background="@drawable/item_bg"
    android:orientation="horizontal"
    android:padding="5dp">

    <!-- 左侧文字布局 -->
    <LinearLayout
        android:id="@+id/ll_info"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical">

        <!-- 新闻标题 -->
        <TextView
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:textColor="#000000"
            android:maxLines="2"
            android:ellipsize="end" />

        <!-- 底部信息布局 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:orientation="horizontal">

            <!-- 用户名 -->
            <TextView
                android:id="@+id/tv_name"
                style="@style/tvInfo"
                android:text="央视新闻客户端" />

            <!-- 评论数 -->
            <TextView
                android:id="@+id/tv_comment"
                style="@style/tvInfo"
                android:text="9884评" />

            <!-- 发布时间 -->
            <TextView
                android:id="@+id/tv_time"
                style="@style/tvInfo"
                android:text="6小时前" />

        </LinearLayout>

    </LinearLayout>

    <!-- 置顶图标 -->
    <ImageView
        android:id="@+id/iv_top"
        style="@style/ivImg"
        android:src="@drawable/top"
        android:layout_marginLeft="5dp" />

    <!-- 新闻图片 -->
    <ImageView
        android:id="@+id/iv_img"
        style="@style/ivImg"
        android:layout_marginLeft="5dp" />

</LinearLayout>

4.3.3 核心控件与属性解析

  1. 根布局:LinearLayout(横向)

    • orientation="horizontal":左文右图布局
    • layout_margin="5dp"padding="5dp":设置条目内边距和外边距,避免内容贴边
    • background="@drawable/item_bg":设置条目白色背景,与浅灰色列表背景区分
  2. 左侧文字布局(ll_info)

    • layout_weight="1":占据横向布局的剩余空间,图片布局在右侧
    • orientation="vertical":垂直排列标题和底部信息
    • id="@+id/ll_info":用于图片样式的 layout_toRightOf 属性,实现左文右图
  3. 新闻标题 TextView(tv_title)

    • maxLines="2":限制最多显示 2 行,避免标题过长
    • ellipsize="end":超出部分用省略号结尾,保证布局美观
    • textSize="16sp":标题字号大于底部文字,突出层级
  4. 底部信息布局

    • 横向排列用户名、评论数、时间,全部引用 @style/tvInfo 统一样式
    • layout_marginTop="5dp":与标题保持间距
  5. ImageView(iv_top、iv_img)

    • 引用 @style/ivImg 统一样式
    • iv_top:置顶图标,默认显示,第一条目(置顶)显示,其他条目隐藏
    • iv_img:新闻图片,无图条目隐藏,单图条目显示
    • 通过代码控制两个 ImageView 的 visibility,实现一个布局适配两种样式

4.4 三图条目布局:list_item_two.xml

4.4.1 布局作用

用于展示3 张图片的条目样式,标题在上,3 张图片横向等宽排列,底部是用户信息。

4.4.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="wrap_content"
    android:layout_margin="5dp"
    android:background="@drawable/item_bg"
    android:orientation="vertical"
    android:padding="5dp">

    <!-- 新闻标题 -->
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:textColor="#000000"
        android:maxLines="2"
        android:ellipsize="end" />

    <!-- 三张图片布局 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:orientation="horizontal">

        <!-- 图片1 -->
        <ImageView
            android:id="@+id/iv_img1"
            style="@style/ivImg"
            android:layout_margin="2dp" />

        <!-- 图片2 -->
        <ImageView
            android:id="@+id/iv_img2"
            style="@style/ivImg"
            android:layout_margin="2dp" />

        <!-- 图片3 -->
        <ImageView
            android:id="@+id/iv_img3"
            style="@style/ivImg"
            android:layout_margin="2dp" />

    </LinearLayout>

    <!-- 底部信息布局 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:orientation="horizontal">

        <!-- 用户名 -->
        <TextView
            android:id="@+id/tv_name"
            style="@style/tvInfo"
            android:text="央视新闻客户端" />

        <!-- 评论数 -->
        <TextView
            android:id="@+id/tv_comment"
            style="@style/tvInfo"
            android:text="9884评" />

        <!-- 发布时间 -->
        <TextView
            android:id="@+id/tv_time"
            style="@style/tvInfo"
            android:text="6小时前" />

    </LinearLayout>

</LinearLayout>

4.4.3 核心控件与属性解析

  1. 根布局:LinearLayout(垂直)

    • orientation="vertical":标题在上,图片在中,底部信息在下
    • 其他属性与单图布局一致,保证条目样式统一
  2. 新闻标题 TextView

    • 与单图布局的标题样式完全一致,保证 UI 统一
    • maxLines="2"ellipsize="end" 限制标题显示
  3. 三张图片布局

    • 横向线性布局,3 个 ImageView 全部引用 @style/ivImg
    • layout_weight="1" 让 3 张图片等宽平分屏幕
    • layout_margin="2dp":图片之间保持间距,避免粘连
  4. 底部信息布局

    • 与单图布局的底部信息完全一致,引用 @style/tvInfo,保证样式统一
    • 用户名、评论数、时间横向排列,层级清晰

五、数据实体类:NewsBean.java

5.1 实体类作用

NewsBean数据模型类,用于封装每条新闻的所有属性,作为 RecyclerView 适配器的数据载体。它遵循 JavaBean 规范,包含私有字段、getter/setter 方法。

5.2 完整代码解析

java

运行

package cn.itcast.headline;

import java.util.List;

public class NewsBean {
    private int id;             // 新闻id
    private String title;       // 新闻标题
    private List<Integer> imgList;  // 新闻图片(存储图片资源id)
    private String name;        // 用户名
    private String comment;     // 用户评论数
    private String time;        // 新闻发布时间
    private int type;           // 新闻类型:1=单图/无图,2=三图

    // 新闻id的getter/setter
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }

    // 新闻标题的getter/setter
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }

    // 用户名的getter/setter
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    // 评论数的getter/setter
    public String getComment() {
        return comment;
    }
    public void setComment(String comment) {
        this.comment = comment;
    }

    // 发布时间的getter/setter
    public String getTime() {
        return time;
    }
    public void setTime(String time) {
        this.time = time;
    }

    // 图片列表的getter/setter
    public List<Integer> getImgList() {
        return imgList;
    }
    public void setImgList(List<Integer> imgList) {
        this.imgList = imgList;
    }

    // 新闻类型的getter/setter
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }
}

5.3 核心字段解析

表格

字段名类型作用
idint新闻唯一标识,用于区分不同条目
titleString新闻标题,显示在条目顶部
imgListList存储图片资源 ID 的列表,单图条目存 1 个,三图条目存 3 个,无图条目存空
nameString用户名,显示在条目底部
commentString评论数,如 “9884 评”
timeString发布时间,如 “6 小时前”
typeint条目类型:1 对应单图 / 无图布局,2 对应三图布局,用于适配器区分条目样式

5.4 设计思路

  • List<Integer> 存储图片,灵活适配单图、三图、无图三种场景
  • type 字段标记条目类型,让适配器可以根据类型加载不同布局
  • 遵循 JavaBean 规范,所有字段私有,通过 getter/setter 访问,保证数据安全

六、核心适配器:NewsAdapter.java 深度解析

适配器是 RecyclerView 的 “灵魂”,本项目的适配器需要处理多类型条目ViewHolder 复用数据绑定,是整个项目最核心的部分。

6.1 适配器核心结构

NewsAdapter 继承自 RecyclerView.Adapter<RecyclerView.ViewHolder>,因为有两种 ViewHolder,所以泛型用父类 RecyclerView.ViewHolder

适配器包含 4 个核心方法:

  1. onCreateViewHolder:创建 ViewHolder,根据类型加载不同布局
  2. getItemViewType:返回条目类型,用于 onCreateViewHolder 区分布局
  3. onBindViewHolder:绑定数据到 ViewHolder
  4. getItemCount:返回条目总数
  5. 两个内部 ViewHolder 类:MyViewViewHolder1MyViewViewHolder2

6.2 完整代码逐行解析

java

运行

package cn.itcast.headline;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

// 适配器继承RecyclerView.Adapter,泛型用父类ViewHolder,支持多类型
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. 根据条目类型创建ViewHolder
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = null;
        RecyclerView.ViewHolder holder = null;
        // 类型1:加载单图/无图布局
        if (viewType == 1) {
            itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_one, parent, false);
            holder = new MyViewViewHolder1(itemView);
        }
        // 类型2:加载三图布局
        else if (viewType == 2) {
            itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_two, parent, false);
            holder = new MyViewViewHolder2(itemView);
        }
        return holder;
    }

    // 2. 返回条目类型,从NewsBean的type字段获取
    @Override
    public int getItemViewType(int position) {
        return NewsList.get(position).getType();
    }

    // 3. 绑定数据到ViewHolder
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        NewsBean bean = NewsList.get(position);
        // 判断ViewHolder类型,绑定对应数据
        if (holder instanceof MyViewViewHolder1) {
            // 单图/无图ViewHolder
            MyViewViewHolder1 viewHolder1 = (MyViewViewHolder1) holder;
            // 绑定标题、用户名、评论数、时间
            viewHolder1.title.setText(bean.getTitle());
            viewHolder1.name.setText(bean.getName());
            viewHolder1.comment.setText(bean.getComment());
            viewHolder1.time.setText(bean.getTime());

            // 控制置顶图标和图片的显示/隐藏
            if (position == 0) {
                // 第一条目:置顶,隐藏新闻图片,显示置顶图标
                viewHolder1.iv_top.setVisibility(View.VISIBLE);
                viewHolder1.iv_img.setVisibility(View.GONE);
            } else {
                // 其他条目:显示新闻图片,隐藏置顶图标
                viewHolder1.iv_top.setVisibility(View.GONE);
                // 图片列表不为空则显示图片
                if (bean.getImgList() != null && bean.getImgList().size() > 0) {
                    viewHolder1.iv_img.setVisibility(View.VISIBLE);
                    viewHolder1.iv_img.setImageResource(bean.getImgList().get(0));
                } else {
                    viewHolder1.iv_img.setVisibility(View.GONE);
                }
            }
        } else if (holder instanceof MyViewViewHolder2) {
            // 三图ViewHolder
            MyViewViewHolder2 viewHolder2 = (MyViewViewHolder2) holder;
            // 绑定标题、用户名、评论数、时间
            viewHolder2.title.setText(bean.getTitle());
            viewHolder2.name.setText(bean.getName());
            viewHolder2.comment.setText(bean.getComment());
            viewHolder2.time.setText(bean.getTime());

            // 绑定3张图片
            if (bean.getImgList() != null && bean.getImgList().size() >= 3) {
                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.size();
    }

    // 单图/无图ViewHolder:缓存条目内的控件
    class MyViewViewHolder1 extends RecyclerView.ViewHolder {
        ImageView iv_top, iv_img;
        TextView title, name, comment, time;

        public MyViewViewHolder1(View itemView) {
            super(itemView);
            // 绑定控件ID,只执行一次,避免重复findViewById
            iv_top = itemView.findViewById(R.id.iv_top);
            iv_img = itemView.findViewById(R.id.iv_img);
            title = itemView.findViewById(R.id.tv_title);
            name = itemView.findViewById(R.id.tv_name);
            comment = itemView.findViewById(R.id.tv_comment);
            time = itemView.findViewById(R.id.tv_time);
        }
    }

    // 三图ViewHolder:缓存条目内的控件
    class MyViewViewHolder2 extends RecyclerView.ViewHolder {
        ImageView iv_img1, iv_img2, iv_img3;
        TextView title, name, comment, time;

        public MyViewViewHolder2(View itemView) {
            super(itemView);
            // 绑定控件ID
            iv_img1 = itemView.findViewById(R.id.iv_img1);
            iv_img2 = itemView.findViewById(R.id.iv_img2);
            iv_img3 = itemView.findViewById(R.id.iv_img3);
            title = itemView.findViewById(R.id.tv_title);
            name = itemView.findViewById(R.id.tv_name);
            comment = itemView.findViewById(R.id.tv_comment);
            time = itemView.findViewById(R.id.tv_time);
        }
    }
}

6.3 核心方法深度解析

6.3.1 构造方法

java

运行

public NewsAdapter(Context context, List<NewsBean> NewsList) {
    this.mContext = context;
    this.NewsList = NewsList;
}
  • 传入 Context:用于加载布局(LayoutInflater
  • 传入 List<NewsBean>:数据列表,适配器的数据源

6.3.2 getItemViewType 方法

java

运行

@Override
public int getItemViewType(int position) {
    return NewsList.get(position).getType();
}
  • 作用:返回当前条目的类型,onCreateViewHolder 根据这个类型加载不同布局
  • 本项目中:1 对应单图 / 无图,2 对应三图
  • 注意:该方法在 onCreateViewHolder 之前调用,必须正确返回类型,否则会加载错误布局

6.3.3 onCreateViewHolder 方法

java

运行

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View itemView = null;
    RecyclerView.ViewHolder holder = null;
    if (viewType == 1) {
        itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_one, parent, false);
        holder = new MyViewViewHolder1(itemView);
    } else if (viewType == 2) {
        itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_two, parent, false);
        holder = new MyViewViewHolder2(itemView);
    }
    return holder;
}
  • 作用:创建条目视图,封装为 ViewHolder,只在需要新 ViewHolder 时调用(条目首次出现、复用池为空时)

  • 核心细节:

    1. LayoutInflater.inflate 第三个参数必须为 false:因为 RecyclerView 会自动将条目添加到父布局,设为 true 会导致重复添加,报错
    2. 根据 viewType 加载不同布局,创建对应 ViewHolder
    3. ViewHolder 只创建一次,后续滚动时复用,避免重复 inflate 布局,提升性能

6.3.4 onBindViewHolder 方法

java

运行

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    NewsBean bean = NewsList.get(position);
    if (holder instanceof MyViewViewHolder1) {
        MyViewViewHolder1 viewHolder1 = (MyViewViewHolder1) holder;
        viewHolder1.title.setText(bean.getTitle());
        viewHolder1.name.setText(bean.getName());
        viewHolder1.comment.setText(bean.getComment());
        viewHolder1.time.setText(bean.getTime());

        // 控制置顶和图片显示
        if (position == 0) {
            viewHolder1.iv_top.setVisibility(View.VISIBLE);
            viewHolder1.iv_img.setVisibility(View.GONE);
        } else {
            viewHolder1.iv_top.setVisibility(View.GONE);
            if (bean.getImgList() != null && bean.getImgList().size() > 0) {
                viewHolder1.iv_img.setVisibility(View.VISIBLE);
                viewHolder1.iv_img.setImageResource(bean.getImgList().get(0));
            } else {
                viewHolder1.iv_img.setVisibility(View.GONE);
            }
        }
    } else if (holder instanceof MyViewViewHolder2) {
        MyViewViewHolder2 viewHolder2 = (MyViewViewHolder2) holder;
        viewHolder2.title.setText(bean.getTitle());
        viewHolder2.name.setText(bean.getName());
        viewHolder2.comment.setText(bean.getComment());
        viewHolder2.time.setText(bean.getTime());

        if (bean.getImgList() != null && bean.getImgList().size() >= 3) {
            viewHolder2.iv_img1.setImageResource(bean.getImgList().get(0));
            viewHolder2.iv_img2.setImageResource(bean.getImgList().get(1));
            viewHolder2.iv_img3.setImageResource(bean.getImgList().get(2));
        }
    }
}
  • 作用:将数据绑定到 ViewHolder 的控件上每次条目显示时都会调用(包括复用)

  • 核心细节:

    1. 通过 instanceof 判断 ViewHolder 类型,强转后绑定数据
    2. 单图 ViewHolder 中,通过 position == 0 判断是否为置顶条目,控制 iv_topiv_img 的显示 / 隐藏
    3. 图片绑定前做非空判断,避免空指针异常
    4. 三图 ViewHolder 中,绑定 3 张图片,从 imgList 中按索引获取

6.3.5 getItemCount 方法

java

运行

@Override
public int getItemCount() {
    return NewsList.size();
}
  • 作用:返回条目总数,RecyclerView 根据这个值决定绘制多少个条目
  • 必须返回正确的数量,否则会出现空白条目或崩溃

6.3.6 ViewHolder 内部类

java

运行

class MyViewViewHolder1 extends RecyclerView.ViewHolder {
    ImageView iv_top, iv_img;
    TextView title, name, comment, time;

    public MyViewViewHolder1(View itemView) {
        super(itemView);
        iv_top = itemView.findViewById(R.id.iv_top);
        iv_img = itemView.findViewById(R.id.iv_img);
        title = itemView.findViewById(R.id.tv_title);
        name = itemView.findViewById(R.id.tv_name);
        comment = itemView.findViewById(R.id.tv_comment);
        time = itemView.findViewById(R.id.tv_time);
    }
}
  • 作用:缓存条目内的所有控件findViewById 只在 ViewHolder 创建时执行一次,后续复用直接使用缓存的控件,大幅提升性能
  • 两个 ViewHolder 分别对应两种条目布局,缓存各自的控件

6.4 多类型条目实现原理

本项目的多类型条目实现,核心是 3 个步骤:

  1. 数据层NewsBean 中添加 type 字段,标记每个条目的类型
  2. 适配器层:重写 getItemViewType,返回 typeonCreateViewHolder 根据 type 加载不同布局,创建不同 ViewHolder
  3. 绑定层onBindViewHolder 根据 ViewHolder 类型,绑定对应数据

这种实现方式是 RecyclerView 多类型条目的标准方案,逻辑清晰、可维护性高。


七、主 Activity:MainActivity.java 数据初始化与列表绑定

7.1 Activity 作用

MainActivity 是项目的入口,负责:

  • 初始化布局,找到 RecyclerView 控件
  • 初始化新闻数据,封装为 NewsBean 列表
  • 创建适配器,设置布局管理器,绑定 RecyclerView
  • 显示列表数据

7.2 完整代码逐行解析

java

运行

package cn.itcast.headline;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
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小时前"
    };

    // 单图条目图片数组(1张图片)
    private int[] icons1 = {
            R.drawable.food,
            R.drawable.takeout
    };

    // 三图条目图片数组(3张图片)
    private int[] icons2 = {
            R.drawable.sleep1,
            R.drawable.sleep2,
            R.drawable.sleep3,
            R.drawable.e_sports,
            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);
    }

    // 初始化数据方法
    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:
                    // 第1条:置顶,无图片
                    List<Integer> imgList0 = new ArrayList<>();
                    bean.setImgList(imgList0);
                    break;
                case 1:
                    // 第2条:单图
                    List<Integer> imgList1 = new ArrayList<>();
                    imgList1.add(icons1[i - 1]);
                    bean.setImgList(imgList1);
                    break;
                case 2:
                    // 第3条:三图
                    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:
                    // 第4条:单图
                    List<Integer> imgList3 = new ArrayList<>();
                    imgList3.add(icons1[i - 2]);
                    bean.setImgList(imgList3);
                    break;
                case 4:
                    // 第5条:三图
                    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:
                    // 第6条:单图
                    List<Integer> imgList5 = new ArrayList<>();
                    imgList5.add(icons1[i - 3]);
                    bean.setImgList(imgList5);
                    break;
            }
            // 将bean添加到数据列表
            NewsList.add(bean);
        }
    }
}

7.3 核心代码解析

7.3.1 数据数组定义

java

运行

private String[] titles = {...};
private String[] names = {...};
private String[] comments = {...};
private String[] times = {...};
private int[] icons1 = {...};
private int[] icons2 = {...};
private int[] types = {1, 1, 2, 1, 2, 1};
  • 定义 6 个数组,分别存储标题、用户名、评论数、时间、单图图片、三图图片、条目类型
  • types 数组标记每个条目的类型,对应 NewsBeantype 字段

7.3.2 onCreate 方法

java

运行

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    setData();
    mRecyclerView = findViewById(R.id.rv_list);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
    mAdapter = new NewsAdapter(MainActivity.this, NewsList);
    mRecyclerView.setAdapter(mAdapter);
}
  • 核心步骤:

    1. setContentView:加载主布局
    2. setData():初始化数据,创建 NewsList
    3. findViewById:找到 RecyclerView 控件
    4. setLayoutManager:设置线性布局管理器,垂直列表
    5. 创建适配器,传入上下文和数据列表
    6. setAdapter:给 RecyclerView 设置适配器,显示列表

7.3.3 setData 方法

java

运行

private void setData() {
    NewsList = new ArrayList<>();
    NewsBean bean;
    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...
        }
        NewsList.add(bean);
    }
}
  • 作用:遍历数组,创建 NewsBean 对象,设置属性,添加到 NewsList

  • 核心细节:

    1. 通过 switch 区分不同条目的图片设置:

      • 第 1 条:空列表,无图(置顶)
      • 第 2、4、6 条:单图,从 icons1 取图片
      • 第 3、5 条:三图,从 icons2 取 3 张图片
    2. 每个 NewsBean 设置 type,对应 types 数组

    3. 最后将 bean 添加到 NewsList,作为适配器的数据源


八、ListView 与 RecyclerView 对比:为什么 RecyclerView 是更好的选择

很多开发者在学习时会疑惑:既然 ListView 也能实现列表,为什么要学 RecyclerView?这里我们做一个全面对比,帮你彻底理解两者的差异。

8.1 性能对比

表格

维度ListViewRecyclerView
视图复用手动实现 convertViewViewHolder,易出错,新手常忘记复用内置 ViewHolder 模式,强制复用,性能更稳定
布局效率每次滚动都需要重新测量布局,效率低布局管理器统一测量,支持预取,滚动更流畅
内存占用无视图回收机制,快速滚动时内存占用高自动回收不可见视图,内存占用更低
局部刷新无原生支持,需手动刷新整个列表支持 notifyItemChanged 等局部刷新,性能更优

8.2 功能对比

表格

功能ListViewRecyclerView
布局方向仅支持垂直列表,横向需自定义支持垂直、水平、网格、瀑布流,灵活度高
条目动画无原生支持,需自定义内置 ItemAnimator,支持增删改查动画
多类型条目需手动在 getView 中判断,逻辑复杂原生支持 getItemViewType,实现优雅
分割线需手动设置 divider,样式单一可自定义 ItemDecoration,灵活实现分割线、吸顶等效果
拖拽与滑动删除无原生支持支持 ItemTouchHelper,快速实现拖拽、侧滑删除

8.3 本项目中 RecyclerView 的优势

在本项目中,RecyclerView 的优势体现得淋漓尽致:

  1. 多类型条目:原生支持两种条目布局,代码逻辑清晰,ListView 实现会非常繁琐
  2. 视图复用:强制 ViewHolder 复用,避免 ListView 中新手常犯的 “忘记复用” 导致的性能问题
  3. 布局灵活:线性布局管理器轻松实现垂直列表,后续可快速改为网格、瀑布流
  4. 性能优化:滚动流畅,即使条目多也不会卡顿,ListView 快速滚动易出现卡顿

九、RecyclerView 常见问题与优化方案

9.1 常见问题与解决方案

9.1.1 条目点击事件

RecyclerView 没有原生的 onItemClickListener,需要在适配器中自定义:

java

运行

// 1. 定义点击回调接口
public interface OnItemClickListener {
    void onItemClick(int position, NewsBean bean);
}
// 2. 在适配器中添加回调变量
private OnItemClickListener listener;
public void setOnItemClickListener(OnItemClickListener listener) {
    this.listener = listener;
}
// 3. 在ViewHolder中设置点击事件
class MyViewViewHolder1 extends RecyclerView.ViewHolder {
    public MyViewViewHolder1(View itemView) {
        super(itemView);
        itemView.setOnClickListener(v -> {
            int position = getAdapterPosition();
            if (listener != null && position != RecyclerView.NO_POSITION) {
                listener.onItemClick(position, NewsList.get(position));
            }
        });
    }
}
// 4. 在Activity中设置监听
mAdapter.setOnItemClickListener((position, bean) -> {
    // 处理点击事件
    Toast.makeText(this, bean.getTitle(), Toast.LENGTH_SHORT).show();
});

9.1.2 条目闪烁问题

当调用 notifyDataSetChanged 时,整个列表会刷新,出现闪烁。解决方案:

  • 使用差分更新 DiffUtil,只刷新变化的条目:

java

运行

public class NewsDiffCallback extends DiffUtil.Callback {
    private List<NewsBean> oldList;
    private List<NewsBean> newList;

    public NewsDiffCallback(List<NewsBean> oldList, List<NewsBean> newList) {
        this.oldList = oldList;
        this.newList = newList;
    }

    @Override
    public int getOldListSize() {
        return oldList.size();
    }

    @Override
    public int getNewListSize() {
        return newList.size();
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        // 判断是否为同一个条目(用id)
        return oldList.get(oldItemPosition).getId() == newList.get(newItemPosition).getId();
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        // 判断内容是否相同
        NewsBean oldBean = oldList.get(oldItemPosition);
        NewsBean newBean = newList.get(newItemPosition);
        return oldBean.getTitle().equals(newBean.getTitle())
                && oldBean.getComment().equals(newBean.getComment());
    }
}
// 更新数据时使用
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new NewsDiffCallback(oldList, newList));
diffResult.dispatchUpdatesTo(mAdapter);

9.1.3 图片加载卡顿

本项目中图片直接用 setImageResource,在列表中快速滚动会出现卡顿。解决方案:

  • 使用图片加载框架(如 Glide、Picasso),异步加载图片:

java

运行

// 以Glide为例
Glide.with(mContext)
    .load(bean.getImgList().get(0))
    .into(viewHolder1.iv_img);

9.2 性能优化方案

  1. 使用 ViewHolder 模式:本项目已经实现,避免重复 findViewById
  2. 避免在 onBindViewHolder 中做耗时操作:如网络请求、复杂计算,异步处理
  3. 使用 DiffUtil 局部刷新:代替 notifyDataSetChanged,减少刷新范围
  4. 设置固定大小mRecyclerView.setHasFixedSize(true),避免重复测量布局
  5. 使用图片加载框架:异步加载,缓存图片,避免卡顿
  6. 优化布局层级:使用 ConstraintLayout 减少布局嵌套,提升测量效率
  7. 预取优化RecyclerView.LayoutManager.setInitialPrefetchItemCount,预取条目,提升滚动流畅度

十、项目总结与拓展

10.1 项目核心知识点总结

通过本项目,我们系统学习了 RecyclerView 的完整使用流程,核心知识点包括:

  1. RecyclerView 核心原理与四大组件:控件、LayoutManager、Adapter、ViewHolder
  2. 多类型条目实现:通过 getItemViewType 区分布局,适配不同样式
  3. 布局设计:主布局、标题栏、单图 / 三图条目布局的设计与控件使用
  4. 数据封装:JavaBean 实体类封装新闻数据
  5. 适配器开发:多类型适配器的完整实现,ViewHolder 复用
  6. 样式与主题配置:统一 UI 风格,抽取样式与颜色
  7. ListView 与 RecyclerView 对比:理解技术选型的原因

10.2 项目拓展方向

本项目是一个基础的 RecyclerView 实战,你可以基于此进行拓展,实现更复杂的功能:

  1. 添加网络请求:从服务器获取新闻数据,代替本地数组
  2. 实现下拉刷新、上拉加载更多:使用 SwipeRefreshLayout + RecyclerView
  3. 添加条目动画:使用 DefaultItemAnimator 或自定义动画
  4. 实现侧滑删除、拖拽排序:使用 ItemTouchHelper
  5. 添加分割线:自定义 ItemDecoration,实现美观的分割线
  6. 优化图片加载:集成 Glide,实现图片缓存、圆角、占位图
  7. 实现吸顶标题:自定义 ItemDecoration,实现分类标题吸顶效果
  8. 适配深色模式:根据系统主题切换颜色样式

结语

RecyclerView 是 Android 开发中最核心的控件之一,掌握它的原理和使用,是每个 Android 开发者的必备技能。

本文通过仿今日头条列表项目,从基础原理到代码实现,从布局设计到适配器开发,全方位拆解了 RecyclerView 的使用流程,希望能帮你彻底吃透这个控件。

部分图片展示

4c520229c11677574375a049bfc537f4.png

05c6115e786eb6b45eae4f4483e5d606.png

ce205fadab3efffb4d2993a420214240.png

90415585422bbb9a6f33d7b35f5bd5b7.jpg

801b5098817dea4f7e12097701b6a76e.jpg

801b5098817dea4f7e12097701b6a76e.jpg

801b5098817dea4f7e12097701b6a76e.jpg