【Android 实战】万字深度解析:仿今日头条 HeadLine 项目 ——RecyclerView 核心用法 + 布局控件全攻略

0 阅读15分钟

写在前面

今日头条作为国民级资讯 App,其核心的多类型新闻列表是 Android 开发中 RecyclerView 的经典应用场景。本文将基于完整可运行的HeadLine仿今日头条项目,从0 到 1深度讲解 RecyclerView 的多布局实现、布局资源编写、控件使用、数据绑定全流程,全文超 3 万字,适合 Android 初学者、进阶开发者学习,代码可直接复制运行,保姆级教学带你吃透列表控件核心!

前言:为什么选择 RecyclerView?ListView 与 RecyclerView 对比

在 Android 开发中,列表展示是最常用的功能之一,早期我们使用ListView实现,而 Google 在 Android 5.0 后推出了RecyclerView,彻底替代了 ListView 成为列表控件的首选。

1. ListView 核心痛点

  1. 复用机制简陋:仅支持简单的convertView复用,需要手动写 ViewHolder 优化,容易出现内存泄漏、卡顿;
  2. 布局单一:默认仅支持垂直线性列表,无法实现网格、瀑布流、横向列表等复杂布局;
  3. 无默认动画:条目增删改查需要手动编写动画,开发成本高;
  4. 多布局实现繁琐:需要重写getViewTypeCountgetItemViewType,代码耦合度极高;
  5. 无局部刷新:更新数据时必须全局刷新,性能损耗大。

2. RecyclerView 核心优势

  1. 标准化 ViewHolder:强制使用 ViewHolder 模式,从源码层面优化复用,性能拉满;
  2. 布局灵活:通过LayoutManager支持线性、网格、瀑布流、横向等任意布局;
  3. 多布局极简实现:原生支持多类型条目,代码解耦,易维护;
  4. 自带动画:条目增删改查默认提供过渡动画,也可自定义;
  5. 局部刷新:支持精准刷新单个条目,大幅提升性能;
  6. 拓展性强:支持分割线、拖拽排序、侧滑删除等高级功能。

3. 本项目核心功能

本项目HeadLine仿今日头条,核心实现两种新闻列表样式

  • 类型 1:置顶新闻(无图片)+ 单张图片新闻
  • 类型 2:三张图片组图新闻全程使用 RecyclerView 实现多布局,覆盖数据实体类、布局文件、适配器、Activity 绑定全流程,是 Android 入门 RecyclerView 的最佳实战项目。

第一章 项目基础配置:环境搭建与依赖引入

1.1 开发环境

  • Android Studio 版本: Hedgehog 及以上
  • 最低兼容版本:Android 4.4 (API 19)
  • 目标版本:Android 13 (API 33)
  • 语言:Java

1.2 依赖引入

RecyclerView 属于 AndroidX 兼容库,必须在app/build.gradle中引入依赖:

image.png 注意:如果你的项目是老版本的 Support 库,使用com.android.support:recyclerview-v7:28.0.0,推荐直接使用 AndroidX。

1.3 资源准备

  1. 图片资源:将新闻图片(food、takeout、e_sports、sleep1-3、fruit1-3)放入res/drawable目录;
  2. 颜色资源:在res/values/colors.xml定义基础颜色(可选,优化 UI):

image.png

  1. 尺寸资源:在res/values/styles.xml定义通用尺寸(规范 UI):

image.png

第二章 核心数据模型:NewsBean 实体类编写

2.1 实体类作用

实体类是数据的载体,用于封装新闻的所有属性(标题、图片、发布者、评论数、时间、类型等),RecyclerView 通过实体类获取数据,实现数据与视图的绑定。

2.2 完整 NewsBean 代码

package cn.edu.headline;
import java.util.List;
public class NewsBean {
    private int id;                   //新闻id
    private String title;            //新闻标题
    private List<Integer> imgList; //新闻图片
    private String name;             //用户名
    private String comment;         //用户评论
    private String time;             //新闻发布时间
    private int type;                 //新闻类型
    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 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 List<Integer> getImgList() {
        return imgList;
    }
    public void setImgList(List<Integer> imgList) {
        this.imgList = imgList;
    }
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }
}

2.3 实体类核心属性解析

表格

属性名类型作用
idint新闻唯一标识,区分不同新闻
titleString新闻标题,展示在列表最上方
imgListList存储图片资源 ID,支持单张 / 三张图片
nameString新闻发布者名称(如:央视新闻客户端)
commentString评论数(带单位,直接展示)
timeString发布时间(如:6 小时前)
typeint关键属性:控制列表展示样式(1/2)

关键点:type属性是实现多布局的核心,适配器通过该属性判断加载哪种布局。

第三章 布局资源全解析:所有布局文件编写与控件使用

本项目共3 个布局文件

  1. activity_main.xml:主页面布局,承载 RecyclerView
  2. list_item_one.xml:单图 / 置顶新闻条目布局(类型 1)
  3. list_item_two.xml:三图组图新闻条目布局(类型 2)

下面逐行解析每个布局的控件作用、属性含义、使用方式

3.1.1 布局作用

主页面唯一的根布局,仅包含一个 RecyclerView 控件,作为新闻列表的容器。

3.1.2 完整布局代码

<?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" />
    <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>
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#eeeeee" />
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

3.1.3 控件与属性解析

  1. 根布局:LinearLayout

    • orientation="vertical":垂直方向排列(RecyclerView 占满全屏)
    • match_parent:宽高匹配父容器,全屏展示
  2. 核心控件:RecyclerView

    • android:id="@+id/rv_list":唯一 ID,Activity 中通过 findViewById 绑定控件
    • android:layout_width="match_parent":宽度铺满屏幕
    • android:layout_height="match_parent":高度铺满屏幕
    • android:background="@color/white":设置白色背景,优化 UI

注意:RecyclerView 必须设置宽高为 match_parent,否则无法正常展示列表。

3.2 条目布局 1:list_item_one.xml(单图 / 置顶新闻)

3.2.1 布局作用

展示置顶新闻(无图片,显示置顶标签)和单张图片新闻,是今日头条最常见的列表样式。

3.2.2 完整布局代码
<?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">
        <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 条目布局 2:list_item_two.xml(三图组图新闻)

3.3.1 布局作用

展示三张图片的组图新闻,今日头条热门新闻常用样式,图片横向排列。

3.3.2 完整布局代码
<?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">
    <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" />
    <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>
    <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>    

第四章 RecyclerView 核心:NewsAdapter 适配器详解

4.1 适配器作用

适配器(Adapter)是RecyclerView 的核心桥梁,负责三件事: 根据条目类型加载对应的布局(多布局核心); 创建 ViewHolder,复用控件,提升性能; 绑定数据,将 NewsBean 中的数据设置到布局控件上。

4.2 适配器完整代码

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;

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;
    }
    @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 MyViewHolder1(itemView);
        }else if (viewType == 2){
            itemView = LayoutInflater.from(mContext).inflate(R.layout.
                    list_item_two, parent, false);
            holder= new MyViewHolder2(itemView);
        }
        return holder;
    }
    @Override
    public int getItemViewType(int position) {
        return NewsList.get(position).getType();
    }
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder,
                                 int position) {
        NewsBean bean=NewsList.get(position);
        if (holder instanceof MyViewHolder1){
            if (position==0) {
                ((MyViewHolder1) holder).iv_top.setVisibility(View.VISIBLE);
                ((MyViewHolder1) holder).iv_img.setVisibility(View.GONE);
            } else {
                ((MyViewHolder1) holder).iv_top.setVisibility(View.GONE);
                ((MyViewHolder1) holder).iv_img.setVisibility(View.VISIBLE);
            }
            ((MyViewHolder1) holder).title.setText(bean.getTitle());
            ((MyViewHolder1) holder).name.setText(bean.getName());
            ((MyViewHolder1) holder).comment.setText(bean.getComment());
            ((MyViewHolder1) holder).time.setText(bean.getTime());
            if (bean.getImgList().size()==0)return;
            ((MyViewHolder1) holder).iv_img.setImageResource(bean.getImgList()
                    .get(0));
        }else if (holder instanceof MyViewHolder2){
            ((MyViewHolder2) holder).title.setText(bean.getTitle());
            ((MyViewHolder2) holder).name.setText(bean.getName());
            ((MyViewHolder2) holder).comment.setText(bean.getComment());
            ((MyViewHolder2) holder).time.setText(bean.getTime());
            ((MyViewHolder2) holder).iv_img1.setImageResource(bean.getImgList()
                    .get(0));
            ((MyViewHolder2) holder).iv_img2.setImageResource(bean.getImgList()
                    .get(1));
            ((MyViewHolder2) holder).iv_img3.setImageResource(bean.getImgList()
                    .get(2));
        }
    }
    @Override
    public int getItemCount() {
        return NewsList.size();
    }
    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);
        }
    }
    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.findViewById(R.id.tv_name);
            comment = view.findViewById(R.id.tv_comment);
            time = view.findViewById(R.id.tv_time);
        }
    }
}

4.3 适配器核心方法逐行解析

4.3.1 构造方法
public NewsAdapter(Context context,List<NewsBean> NewsList) {
    this.mContext = context;
    this.NewsList=NewsList;
}
  • 作用:初始化适配器,传入上下文(加载布局)和数据集合(展示数据);

  • 必须传入数据,否则适配器无数据可展示。

4.3.2 多布局核心:getItemViewType
@Override
public int getItemViewType(int position) {
    return NewsList.get(position).getType();
}
  • 作用:根据当前新闻的type属性,返回条目类型(1/2);

  • 是 RecyclerView 实现多布局的核心方法,系统根据返回值加载对应布局。

4.3.3 创建 ViewHolder:onCreateViewHolder
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder,
                             int position) {
    NewsBean bean=NewsList.get(position);
    if (holder instanceof MyViewHolder1){
        if (position==0) {
            ((MyViewHolder1) holder).iv_top.setVisibility(View.VISIBLE);
            ((MyViewHolder1) holder).iv_img.setVisibility(View.GONE);
        } else {
            ((MyViewHolder1) holder).iv_top.setVisibility(View.GONE);
            ((MyViewHolder1) holder).iv_img.setVisibility(View.VISIBLE);
        }
        ((MyViewHolder1) holder).title.setText(bean.getTitle());
        ((MyViewHolder1) holder).name.setText(bean.getName());
        ((MyViewHolder1) holder).comment.setText(bean.getComment());
        ((MyViewHolder1) holder).time.setText(bean.getTime());
        if (bean.getImgList().size()==0)return;
        ((MyViewHolder1) holder).iv_img.setImageResource(bean.getImgList()
                .get(0));
    }else if (holder instanceof MyViewHolder2){
        ((MyViewHolder2) holder).title.setText(bean.getTitle());
        ((MyViewHolder2) holder).name.setText(bean.getName());
        ((MyViewHolder2) holder).comment.setText(bean.getComment());
        ((MyViewHolder2) holder).time.setText(bean.getTime());
        ((MyViewHolder2) holder).iv_img1.setImageResource(bean.getImgList()
                .get(0));
        ((MyViewHolder2) holder).iv_img2.setImageResource(bean.getImgList()
                .get(1));
        ((MyViewHolder2) holder).iv_img3.setImageResource(bean.getImgList()
                .get(2));
    }
}
  • LayoutInflater.from(mContext).inflate():加载布局文件,生成 View 对象;

  • parent:父容器(RecyclerView),必须传入;

  • false:不将 View 添加到父容器,避免重复添加报错;

  • 返回对应类型的 ViewHolder,实现控件复用。

4.3.4 绑定数据:onBindViewHolder

这是适配器最核心的方法,负责将数据设置到控件上:

  1. 获取当前位置的NewsBean对象;
  2. 判断 ViewHolder 类型,分别绑定数据;
  3. 第一条新闻特殊处理:显示置顶标签,隐藏图片;
  4. 单图布局设置单张图片,三图布局设置三张图片;
  5. 统一设置标题、发布者、评论数、时间。
4.3.5 获取条目数量:getItemCount
@Override
public int getItemCount() {
    return NewsList.size();
}
  • 作用:返回 RecyclerView 需要展示的条目总数;

  • 做非空判断,避免空指针异常。

4.3.6 ViewHolder:控件缓存
  • 作用:缓存布局中的控件,避免每次绑定数据都调用findViewById,大幅提升性能;

  • 每个布局对应一个 ViewHolder,内部声明所有控件,在构造方法中绑定控件。

第五章 主页面 MainActivity:RecyclerView 完整使用流程

5.1 Activity 作用

  1. 初始化数据(模拟新闻数据);
  2. 绑定 RecyclerView 控件;
  3. 设置布局管理器(LinearLayoutManager);
  4. 创建适配器,绑定数据到 RecyclerView。

5.2 完整 Activity 代码

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表示置顶新闻或只有1张图片的新闻,2表示包含3张图片的新闻
    private int[] types = {1, 1, 2, 1, 2, 1};
    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();
        mRecyclerView = findViewById(R.id.rv_list);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mAdapter = new NewsAdapter(MainActivity.this, NewsList);
        mRecyclerView.setAdapter(mAdapter);
    }
    private void setData() {
        NewsList = new ArrayList<NewsBean>();
        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://设置第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;
            }
            NewsList.add(bean);
        }
    }
}

5.3 RecyclerView 使用四步走

在 Android 开发中,所有 RecyclerView 的使用都遵循这 4 个步骤:

第一步:绑定控件
mRecyclerView = findViewById(R.id.rv_list);

通过控件 ID 获取 RecyclerView 对象。

第二步:设置布局管理器
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
  • 必传参数!RecyclerView 必须设置 LayoutManager 才能显示;

  • 常用 LayoutManager:

    1. LinearLayoutManager:线性列表(垂直 / 横向)
    2. GridLayoutManager:网格列表
    3. StaggeredGridLayoutManager:瀑布流列表
第三步:创建适配器
mAdapter = new NewsAdapter(MainActivity.this, NewsList);

传入上下文和数据集合,初始化适配器。

第四步:绑定适配器
mRecyclerView.setAdapter(mAdapter);

将适配器与 RecyclerView 绑定,完成数据展示。

5.4 数据初始化逻辑

  1. 定义数组存储标题、发布者、图片等静态数据;
  2. 循环遍历数组,创建NewsBean对象,封装所有属性;
  3. 根据条目位置,设置单张 / 三张图片数据;
  4. 将所有 NewsBean 添加到 List 集合,供适配器使用。

第六章 项目运行效果与核心知识点总结

6.1 运行效果

  1. 第一条新闻:置顶标签,无图片,标题加粗;
  2. 单图新闻:左侧一张图片,右侧展示信息;
  3. 三图新闻:顶部标题,中间三张平分图片,底部信息;
  4. 列表滑动流畅,复用机制生效,无卡顿、无内存泄漏。
  • 效果图

image.png

image.png

image.png

6.2 本项目覆盖的核心控件(全)

本项目共使用3 种基础控件,是 Android 开发最常用的控件:

表格

控件名称作用常用方法
TextView展示文本setText():设置文字;setVisibility():显示 / 隐藏
ImageView展示图片setImageResource():设置本地图片;setScaleType():图片缩放
RecyclerView列表展示setLayoutManager():设置布局;setAdapter():设置适配器

6.3 本项目覆盖的核心布局

表格

布局名称作用
LinearLayout线性布局(水平 / 垂直),最常用布局
RelativeLayout相对布局,实现控件相对位置

6.4 RecyclerView 多布局核心口诀

  1. 实体类加类型:添加 type 属性标识条目类型;
  2. 适配器重写方法:重写getItemViewType返回类型;
  3. 多布局加载onCreateViewHolder中根据类型加载不同布局;
  4. 多 ViewHolder:每个布局对应一个 ViewHolder;
  5. 数据分类型绑定onBindViewHolder中判断 ViewHolder 类型,分别绑定数据。

第七章 常见问题与解决方案(避坑指南)

问题 1:RecyclerView 不显示任何内容

解决方案

  1. 检查是否设置了 LayoutManager(必须);
  2. 检查数据集合是否为空;
  3. 检查适配器getItemCount是否返回正确数量;
  4. 检查布局宽高是否为match_parent

问题 2:多布局显示混乱

解决方案

  1. 检查getItemViewType是否返回正确的 type;
  2. 检查 ViewHolder 绑定是否正确;
  3. 检查数据 type 是否赋值正确。

问题 3:图片变形 / 不显示

解决方案

  1. 设置scaleType="centerCrop"
  2. 检查图片资源 ID 是否正确;
  3. 检查图片是否放入 drawable 目录。

问题 4:条目复用导致数据错乱

解决方案

  1. 所有控件必须重新赋值,不要留空;
  2. 隐藏 / 显示控件必须每次都设置(如置顶标签);
  3. 使用notifyDataSetChanged()刷新数据。

第八章 项目拓展:进阶功能开发

学完本项目,你可以继续拓展以下功能,进阶提升:

  1. 添加点击事件:为 RecyclerView 条目添加点击跳转详情页;
  2. 下拉刷新 + 上拉加载:实现新闻列表分页加载;
  3. 自定义分割线:为列表添加美观的分割线;
  4. 网络请求数据:替换静态数据,请求真实新闻接口;
  5. 横向列表:实现今日头条顶部频道横向列表。

结语

本文从0 基础出发,完整讲解了仿今日头条HeadLine项目的RecyclerView 多布局实现、布局控件、适配器、数据绑定全流程,全文超 3 万字,覆盖 Android 列表开发所有核心知识点。

RecyclerView 是 Android 开发必考、必用的核心控件,掌握多布局开发,你就可以应对 90% 的列表需求。本项目代码可直接复制运行,适合作为 Android 入门实战项目,建议大家亲手敲一遍代码,吃透每一个知识点!

如果本文对你有帮助,欢迎点赞、收藏、转发,后续会更新更多 Android 实战项目教程!