仿今日头条项目RecyclerView及布局资源详解

6 阅读22分钟

仿今日头条项目RecyclerView及布局资源详解

目录

  1. 项目概述
  2. RecyclerView基本概念
  3. 项目核心代码分析
  4. 布局资源详解
  5. 控件和样式配置
  6. 总结

1. 项目概述

1.1 项目简介

本项目是一个仿今日头条的Android新闻资讯应用,采用传统的Android开发技术栈,主要使用RecyclerView来展示不同类型的新闻条目。项目结构清晰,代码规范,是学习Android列表展示的优秀案例。

屏幕截图 2026-03-31 155011.png

1.2 项目结构

image.png


2. RecyclerView基本概念

2.1 什么是RecyclerView

RecyclerView是Android Support Library中提供的一个高级UI组件,用于在有限的窗口内展示大量数据集合。它是ListView的升级版,具有更好的性能和灵活性。

2.2 RecyclerView的优势

  1. ViewHolder模式:强制使用ViewHolder模式,避免了频繁的findViewById操作
  2. 布局管理器:通过LayoutManager可以轻松实现线性布局、网格布局、瀑布流布局等
  3. Item动画:内置了默认的Item添加、删除、移动动画
  4. Item装饰:通过ItemDecoration可以自定义Item之间的分隔线
  5. 多类型布局:支持多种Item布局类型,非常适合复杂的列表展示

2.3 RecyclerView的核心组件

  1. RecyclerView:视图容器,负责显示列表
  2. Adapter:适配器,负责数据与视图的绑定
  3. LayoutManager:布局管理器,负责Item的布局方式
  4. ViewHolder:视图持有者,用于缓存Item视图
  5. ItemDecoration:Item装饰,用于绘制Item之间的分隔线等
  6. ItemAnimator:Item动画,负责Item添加、删除、移动时的动画效果

3. 项目核心代码分析

3.1 NewsBean.java - 新闻数据模型

3.1.1 代码结构

image.png

3.1.2 字段详解
字段名类型说明
idint新闻唯一标识符,用于区分不同的新闻条目
titleString新闻标题,显示在新闻条目的主要位置
imgListList<Integer>新闻图片列表,存储图片资源的ID,支持多张图片
nameString新闻发布者的名称或来源
commentString评论数量,格式如"9884评"
timeString新闻发布时间,格式如"6小时前"、"刚刚"
typeint新闻类型,1表示单图或置顶新闻,2表示三图新闻
3.1.3 设计思想

NewsBean采用了经典的Java Bean设计模式:

  1. 私有化字段:所有字段都使用private修饰符,保证数据封装性
  2. 提供getter/setter方法:通过公共方法访问和修改字段
  3. 无参构造函数:虽然代码中没有显式定义,但Java会默认提供
  4. 支持多种图片:使用List<Integer>存储图片,灵活支持0-3张图片

3.2 MainActivity.java - 主Activity

3.2.1 类声明和成员变量
public class MainActivity extends AppCompatActivity {
    // 新闻标题数组
    private String[] titles = {...};
    // 新闻来源数组
    private String[] names = {...};
    // 评论数量数组
    private String[] comments = {...};
    // 发布时间数组
    private String[] times = {...};
    // 单图新闻的图片资源数组
    private int[] icons1 = {...};
    // 三图新闻的图片资源数组
    private int[] icons2 = {...};
    // 新闻类型数组,1表示单图,2表示三图
    private int[] types = {1, 1, 2, 1, 2, 1};
    
    private RecyclerView mRecyclerView;
    private NewsAdapter mAdapter;
    private List<NewsBean> NewsList;
3.2.2 数据准备说明

image.png

3.2.3 onCreate方法 - 初始化界面

image.png

详细步骤解析

  1. 调用父类onCreatesuper.onCreate(savedInstanceState) - 必须调用,确保Activity正确初始化
  2. 加载布局setContentView(R.layout.activity_main) - 加载主界面布局文件
  3. 准备数据setData() - 调用自定义方法,准备新闻数据
  4. 查找RecyclerViewmRecyclerView = findViewById(R.id.rv_list) - 通过ID找到RecyclerView控件
  5. 设置布局管理器mRecyclerView.setLayoutManager(new LinearLayoutManager(this)) - 设置为线性布局管理器
  6. 创建适配器mAdapter = new NewsAdapter(MainActivity.this, NewsList) - 创建适配器实例
  7. 设置适配器mRecyclerView.setAdapter(mAdapter) - 将适配器绑定到RecyclerView

LayoutManager的作用

LinearLayoutManager是RecyclerView提供的布局管理器之一,它的特点是:

  • 以线性方式排列Item
  • 支持垂直和水平两种方向(默认垂直)
  • 可以设置反转布局
  • 可以设置从右到左布局

本项目使用默认的垂直方向LinearLayoutManager,Item从上到下依次排列。

3.2.4 setData方法 - 数据组装
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);
    }
}

详细解析

  1. 初始化列表NewsList = new ArrayList<NewsBean>() - 创建ArrayList用于存储新闻数据
  2. 循环组装数据:使用for循环遍历titles数组,逐个创建NewsBean对象
  3. 设置基本属性:设置id、title、name、comment、time、type等基本属性
  4. 设置图片列表:使用switch语句根据不同的位置设置不同的图片列表
  5. 添加到列表:将组装好的NewsBean对象添加到NewsList中

图片设置逻辑

  • 第0条新闻(i=0):创建空的imgList,不显示任何图片,只显示置顶标识
  • 第1条新闻(i=1):从icons1数组中取第0个元素(food.png)
  • 第2条新闻(i=2):从icons2数组中取第0、1、2个元素(sleep1、sleep2、sleep3.png)
  • 第3条新闻(i=3):从icons1数组中取第1个元素(takeout.png)
  • 第4条新闻(i=4):从icons2数组中取第3、4、5个元素(fruit1、fruit2、fruit3.png)
  • 第5条新闻(i=5):从icons1数组中取第2个元素(e_sports.png)

3.3 NewsAdapter.java - RecyclerView适配器

3.3.1 类声明和成员变量
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. 继承RecyclerView.Adapter:这是RecyclerView适配器的基类
  2. 泛型参数:使用RecyclerView.ViewHolder作为泛型参数,因为我们有两种ViewHolder类型
  3. 构造函数:接收Context和数据列表作为参数
  4. 成员变量:保存Context和数据列表的引用
3.3.2 getItemViewType方法 - 获取Item类型
@Override
public int getItemViewType(int position) {
    return NewsList.get(position).getType();
}

作用说明

这个方法的作用是根据position返回对应的Item类型。RecyclerView会根据这个返回值来决定使用哪个ViewHolder来显示该位置的Item。

在本项目中:

  • 返回1表示使用单图/置顶新闻布局
  • 返回2表示使用三图新闻布局
3.3.3 onCreateViewHolder方法 - 创建ViewHolder
@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;
}

详细解析

  1. LayoutInflater的使用

    • LayoutInflater用于将XML布局文件转换为View对象
    • inflate()方法的三个参数:
      • 第一个参数:布局资源ID
      • 第二个参数:父ViewGroup
      • 第三个参数:是否将生成的View添加到父ViewGroup中(这里传false,因为RecyclerView会自动添加)
  2. 根据viewType创建不同的ViewHolder

    • 当viewType == 1时,加载list_item_one.xml布局,创建MyViewHolder1
    • 当viewType == 2时,加载list_item_two.xml布局,创建MyViewHolder2
  3. ViewHolder的作用

    • ViewHolder用于缓存View对象,避免重复调用findViewById
    • 每个ViewHolder对应一个Item的布局
    • ViewHolder的创建数量是有限的,一般比屏幕上能显示的Item数量多几个
3.3.4 onBindViewHolder方法 - 绑定数据
@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));
    }
}

详细解析

  1. 获取数据NewsBean bean = NewsList.get(position) - 获取当前位置的数据对象

  2. 处理MyViewHolder1(单图/置顶新闻)

    • 判断是否为置顶新闻
      • 如果position == 0,显示置顶标识(iv_top),隐藏图片(iv_img)
      • 否则,隐藏置顶标识,显示图片
    • 设置文本数据:设置标题、来源、评论、时间
    • 设置图片:如果图片列表不为空,设置第一张图片
  3. 处理MyViewHolder2(三图新闻)

    • 设置文本数据:设置标题、来源、评论、时间
    • 设置图片:依次设置三张图片
  4. instanceof关键字

    • 用于判断对象是否是某个类的实例
    • 这里用于判断holder是MyViewHolder1还是MyViewHolder2的实例
    • 判断后需要进行强制类型转换
3.3.5 getItemCount方法 - 获取Item数量
@Override
public int getItemCount() {
    return NewsList.size();
}

作用说明

这个方法返回RecyclerView中Item的总数量,RecyclerView会根据这个值来决定需要创建多少个ViewHolder以及需要绑定多少次数据。

3.3.6 MyViewHolder1 - 单图/置顶新闻ViewHolder
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);
    }
}

字段说明

字段名类型说明
iv_topImageView置顶标识图片
iv_imgImageView新闻图片
titleTextView新闻标题
nameTextView新闻来源
commentTextView评论数量
timeTextView发布时间
3.3.7 MyViewHolder2 - 三图新闻ViewHolder
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);
    }
}

字段说明

字段名类型说明
iv_img1ImageView第一张新闻图片
iv_img2ImageView第二张新闻图片
iv_img3ImageView第三张新闻图片
titleTextView新闻标题
nameTextView新闻来源
commentTextView评论数量
timeTextView发布时间

4. 布局资源详解

4.1 activity_main.xml - 主界面布局

image.png

4.1.1 完整代码
<?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>
4.1.2 根布局 - LinearLayout
<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">

属性详解

属性名属性值说明
layout_widthmatch_parent宽度匹配父容器(填满整个屏幕宽度)
layout_heightmatch_parent高度匹配父容器(填满整个屏幕高度)
background@color/light_gray_color背景颜色,使用颜色资源引用
orientationvertical子元素垂直排列
4.1.3 include标签 - 引入标题栏
<include layout="@layout/title_bar" />

作用说明

include标签用于在一个布局文件中引入另一个布局文件,这样可以:

  • 提高代码复用性
  • 便于统一修改
  • 使布局文件更加清晰

这里引入了title_bar.xml布局文件作为标题栏。

4.1.4 标签栏 - LinearLayout
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:background="@android:color/white"
    android:orientation="horizontal">

属性详解

属性名属性值说明
layout_widthmatch_parent宽度填满屏幕
layout_height40dp高度固定为40dp
background@android:color/white背景颜色为白色,使用系统颜色资源
orientationhorizontal子元素水平排列

标签TextView

<TextView
    style="@style/tvStyle"
    android:text="推荐"
    android:textColor="@android:color/holo_red_dark" />
  • 使用style属性引用tvStyle样式
  • "推荐"标签使用红色文字,表示当前选中状态
  • 其他标签使用灰色文字
4.1.5 分隔线 - View
<View
    android:layout_width="match_parent"
    android:layout_height="1dp"
    android:background="#eeeeee" />

作用说明

使用一个简单的View作为分隔线:

  • 宽度填满屏幕
  • 高度为1dp
  • 背景颜色为浅灰色(#eeeeee)
4.1.6 RecyclerView控件
<android.support.v7.widget.RecyclerView
    android:id="@+id/rv_list"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

属性详解

属性名属性值说明
id@+id/rv_list控件唯一标识符,用于在Java代码中查找
layout_widthmatch_parent宽度填满屏幕
layout_heightmatch_parent高度填满剩余空间

4.2 title_bar.xml - 标题栏布局

image.png

4.2.1 完整代码
<?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">
    
    <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_marginStart="15dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="15dp"
        android:background="@drawable/search_bg"
        android:gravity="center_vertical"
        android:textColor="@android:color/black"
        android:hint="搜你想搜的"
        android:textColorHint="@color/gray_color"
        android:textSize="14sp"
        android:paddingLeft="30dp" />
</LinearLayout>
4.2.2 根布局 - LinearLayout
<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">

属性详解

属性名属性值说明
layout_widthmatch_parent宽度填满屏幕
layout_height50dp高度固定为50dp
background#d33d3c背景颜色为红色(今日头条的主题色)
orientationhorizontal子元素水平排列
paddingLeft10dp左边内边距
paddingRight10dp右边内边距
4.2.3 标题TextView
<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" />

属性详解

属性名属性值说明
layout_widthwrap_content宽度自适应内容
layout_heightwrap_content高度自适应内容
layout_gravitycenter在父容器中居中
text仿今日头条显示的文本
textColor@android:color/white文字颜色为白色
textSize22sp文字大小为22sp
4.2.4 搜索框EditText
<EditText
    android:layout_width="match_parent"
    android:layout_height="35dp"
    android:layout_gravity="center_vertical"
    android:layout_marginStart="15dp"
    android:layout_marginLeft="5dp"
    android:layout_marginRight="15dp"
    android:background="@drawable/search_bg"
    android:gravity="center_vertical"
    android:textColor="@android:color/black"
    android:hint="搜你想搜的"
    android:textColorHint="@color/gray_color"
    android:textSize="14sp"
    android:paddingLeft="30dp" />

属性详解

属性名属性值说明
layout_widthmatch_parent宽度填满剩余空间
layout_height35dp高度固定为35dp
layout_gravitycenter_vertical垂直居中
layout_marginStart15dp起始边距(兼容RTL布局)
layout_marginLeft5dp左边距
layout_marginRight15dp右边距
background@drawable/search_bg背景使用search_bg drawable资源
gravitycenter_vertical文字垂直居中
textColor@android:color/black输入文字颜色为黑色
hint搜你想搜的提示文字
textColorHint@color/gray_color提示文字颜色为灰色
textSize14sp文字大小为14sp
paddingLeft30dp左边内边距(为搜索图标预留空间)

4.3 list_item_one.xml - 单图/置顶新闻Item布局

image.png

4.3.1 完整代码
<?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>
4.3.2 根布局 - RelativeLayout
<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">

属性详解

属性名属性值说明
layout_widthmatch_parent宽度填满屏幕
layout_height90dp高度固定为90dp
layout_marginBottom8dp底部外边距,与下一个Item保持距离
background@android:color/white背景颜色为白色
padding8dp四周内边距
4.3.3 信息容器 - LinearLayout
<LinearLayout
    android:id="@+id/ll_info"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">

属性详解

属性名属性值说明
id@+id/ll_info控件唯一标识符
layout_widthwrap_content宽度自适应内容
layout_heightwrap_content高度自适应内容
orientationvertical子元素垂直排列
4.3.4 标题TextView
<TextView
    android:id="@+id/tv_title"
    android:layout_width="280dp"
    android:layout_height="wrap_content"
    android:maxLines="2"
    android:textColor="#3c3c3c"
    android:textSize="16sp" />

属性详解

属性名属性值说明
id@+id/tv_title控件唯一标识符
layout_width280dp宽度固定为280dp
layout_heightwrap_content高度自适应内容
maxLines2最多显示2行,超出部分省略
textColor#3c3c3c文字颜色为深灰色
textSize16sp文字大小为16sp
4.3.5 底部信息容器 - RelativeLayout
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

作用说明

用于放置置顶标识和来源、评论、时间信息。

4.3.6 置顶标识ImageView
<ImageView
    android:id="@+id/iv_top"
    android:layout_width="20dp"
    android:layout_height="20dp"
    android:layout_alignParentBottom="true"
    android:src="@drawable/top" />

属性详解

属性名属性值说明
id@+id/iv_top控件唯一标识符
layout_width20dp宽度固定为20dp
layout_height20dp高度固定为20dp
layout_alignParentBottomtrue与父容器底部对齐
src@drawable/top图片资源为top.png
4.3.7 底部信息LinearLayout
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_toRightOf="@id/iv_top"
    android:orientation="horizontal">

属性详解

属性名属性值说明
layout_widthmatch_parent宽度填满剩余空间
layout_heightwrap_content高度自适应内容
layout_alignParentBottomtrue与父容器底部对齐
layout_toRightOf@id/iv_top在iv_top的右边
orientationhorizontal子元素水平排列

三个信息TextView

<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" />
  • 都使用tvInfo样式
  • 分别用于显示来源、评论、时间
4.3.8 新闻图片ImageView
<ImageView
    android:id="@+id/iv_img"
    android:layout_width="match_parent"
    android:layout_height="90dp"
    android:layout_toRightOf="@id/ll_info"
    android:padding="3dp" />

属性详解

属性名属性值说明
id@+id/iv_img控件唯一标识符
layout_widthmatch_parent宽度填满剩余空间
layout_height90dp高度固定为90dp
layout_toRightOf@id/ll_info在ll_info的右边
padding3dp四周内边距

4.4 list_item_two.xml - 三图新闻Item布局

image.png

4.4.1 完整代码
<?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>
4.4.2 根布局 - RelativeLayout
<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">

属性详解

属性名属性值说明
layout_widthmatch_parent宽度填满屏幕
layout_heightwrap_content高度自适应内容(因为有三张图片,高度不固定)
layout_marginBottom8dp底部外边距
background@android:color/white背景颜色为白色
4.4.3 标题TextView
<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" />

属性详解

属性名属性值说明
id@+id/tv_title控件唯一标识符
layout_widthmatch_parent宽度填满屏幕
layout_heightwrap_content高度自适应内容
maxLines2最多显示2行
padding8dp四周内边距
textColor#3c3c3c文字颜色为深灰色
textSize16sp文字大小为16sp
4.4.4 图片容器 - LinearLayout
<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">

属性详解

属性名属性值说明
id@+id/ll_img控件唯一标识符
layout_widthmatch_parent宽度填满屏幕
layout_heightwrap_content高度自适应内容
layout_below@id/tv_title在tv_title的下方
orientationhorizontal子元素水平排列
4.4.5 三张图片ImageView
<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"/>
  • 都使用ivImg样式
  • 三张图片等宽排列
4.4.6 底部信息容器
<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>

结构说明

  • 外层LinearLayout在ll_img的下方
  • 内层LinearLayout水平放置三个信息TextView
  • 三个信息TextView都使用tvInfo样式

5. 控件和样式配置

5.1 colors.xml - 颜色资源

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
    <color name="light_gray_color">#eeeeee</color>
    <color name="gray_color">#828282</color>
</resources>

颜色详解

颜色名称颜色值说明使用位置
colorPrimary#008577主题主色调AppBar等
colorPrimaryDark#00574B深色主色调状态栏等
colorAccent#D81B60强调色开关、复选框等
light_gray_color#eeeeee浅灰色主界面背景、分隔线
gray_color#828282灰色未选中标签、提示文字

5.2 styles.xml - 样式资源

<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
    
    <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>
    
    <style name="ivImg" >
        <item name="android:layout_width">0dp</item>
        <item name="android:layout_height">90dp</item>
        <item name="android:layout_weight">1</item>
        <item name="android:layout_toRightOf">@id/ll_info</item>
    </style>
</resources>
5.2.1 AppTheme - 应用主题
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>

说明

  • 继承自Theme.AppCompat.Light.DarkActionBar
  • 设置了三个主题色
5.2.2 tvStyle - 标签样式
<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>

使用位置:activity_main.xml中的标签TextView

属性说明

  • 宽度自适应内容
  • 高度填满父容器
  • 内边距10dp
  • 文字居中
  • 文字大小15sp
5.2.3 tvInfo - 信息文字样式
<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>

使用位置

  • list_item_one.xml中的来源、评论、时间TextView
  • list_item_two.xml中的来源、评论、时间TextView

属性说明

  • 宽高自适应内容
  • 左边距8dp
  • 垂直居中
  • 文字大小14sp
  • 文字颜色为灰色
5.2.4 ivImg - 图片样式
<style name="ivImg" >
    <item name="android:layout_width">0dp</item>
    <item name="android:layout_height">90dp</item>
    <item name="android:layout_weight">1</item>
    <item name="android:layout_toRightOf">@id/ll_info</item>
</style>

使用位置:list_item_two.xml中的三张图片ImageView

属性说明

  • 宽度0dp(配合layout_weight使用)
  • 高度90dp
  • layout_weight为1(三张图片等宽)
  • 在ll_info的右边(这个属性在list_item_two中不会生效,因为父容器不同)

5.3 图片资源

项目使用了以下图片资源(位于drawable-hdpi目录):

图片文件名说明使用位置
food.png美食图片第二条新闻
takeout.png外卖图片第四条新闻
e_sports.png电竞图片第六条新闻
sleep1.png睡眠图片1第三条新闻第1张
sleep2.png睡眠图片2第三条新闻第2张
sleep3.png睡眠图片3第三条新闻第3张
fruit1.png水果图片1第五条新闻第1张
fruit2.png水果图片2第五条新闻第2张
fruit3.png水果图片3第五条新闻第3张
top.png置顶标识第一条新闻
search_bg.png搜索框背景标题栏搜索框

6. 总结

6.1 项目技术要点回顾

  1. RecyclerView的使用

    • 使用RecyclerView展示新闻列表
    • 实现了多类型Item布局(单图/置顶、三图)
    • 使用LinearLayoutManager实现线性布局
  2. Adapter的设计

    • 继承RecyclerView.Adapter
    • 重写getItemViewType方法支持多类型
    • 实现两个ViewHolder类
    • 在onBindViewHolder中根据ViewHolder类型绑定数据
  3. ViewHolder模式

    • 强制使用ViewHolder
    • 在ViewHolder构造函数中findViewById
    • 避免了重复的findViewById操作,提高性能
  4. 布局文件的设计

    • 使用include标签复用标题栏
    • 使用style标签统一样式
    • 合理使用LinearLayout和RelativeLayout
    • 使用shape drawable作为背景
  5. 数据模型的设计

    • 使用Java Bean模式
    • 使用List存储多张图片
    • 使用type字段区分Item类型

6.2 RecyclerView的工作原理

  1. 初始化阶段

    • 创建RecyclerView实例
    • 设置LayoutManager
    • 创建Adapter实例
    • 设置Adapter
  2. 布局阶段

    • LayoutManager测量和布局Item
    • 根据屏幕大小决定显示多少个Item
    • 创建足够的ViewHolder
  3. 滚动阶段

    • 当Item滚出屏幕时,ViewHolder被回收
    • 当新的Item要进入屏幕时,使用回收的ViewHolder
    • 调用onBindViewHolder重新绑定数据
  4. 回收机制

    • RecyclerView维护一个回收池
    • 回收的ViewHolder可以被重复利用
    • 大大减少了创建对象的开销

6.3 项目的可扩展性

  1. 添加更多Item类型

    • 在NewsBean中添加新的type值
    • 创建新的ViewHolder类
    • 创建新的布局文件
    • 在Adapter中添加对应的逻辑
  2. 添加网络请求

    • 使用OkHttp或Retrofit
    • 从服务器获取新闻数据
    • 解析JSON数据
    • 更新NewsList并调用notifyDataSetChanged
  3. 添加下拉刷新和上拉加载

    • 使用SwipeRefreshLayout
    • 监听RecyclerView的滚动事件
    • 实现加载更多逻辑
  4. 添加Item点击事件

    • 在Adapter中定义接口
    • 在Activity中实现接口
    • 在onBindViewHolder中设置点击事件
  5. 添加Item动画

    • 使用默认的ItemAnimator
    • 自定义ItemAnimator
    • 实现添加、删除、移动动画

6.4 最佳实践

  1. 使用ViewHolder

    • 必须使用ViewHolder模式
    • 在ViewHolder中缓存所有View引用
  2. 避免在onBindViewHolder中做耗时操作

    • 不要在onBindViewHolder中加载图片
    • 使用图片加载库(如Glide、Picasso)
    • 提前准备好数据
  3. 使用DiffUtil

    • 当数据变化时,使用DiffUtil计算差异
    • 只更新变化的Item
    • 提高性能和用户体验
  4. 合理使用布局管理器

    • LinearLayoutManager:线性列表
    • GridLayoutManager:网格布局
    • StaggeredGridLayoutManager:瀑布流
  5. 使用ItemDecoration

    • 自定义Item之间的分隔线
    • 可以设置不同的间距
    • 可以绘制装饰