仿今日头条项目RecyclerView及布局资源详解
目录
1. 项目概述
1.1 项目简介
本项目是一个仿今日头条的Android新闻资讯应用,采用传统的Android开发技术栈,主要使用RecyclerView来展示不同类型的新闻条目。项目结构清晰,代码规范,是学习Android列表展示的优秀案例。
1.2 项目结构
2. RecyclerView基本概念
2.1 什么是RecyclerView
RecyclerView是Android Support Library中提供的一个高级UI组件,用于在有限的窗口内展示大量数据集合。它是ListView的升级版,具有更好的性能和灵活性。
2.2 RecyclerView的优势
- ViewHolder模式:强制使用ViewHolder模式,避免了频繁的findViewById操作
- 布局管理器:通过LayoutManager可以轻松实现线性布局、网格布局、瀑布流布局等
- Item动画:内置了默认的Item添加、删除、移动动画
- Item装饰:通过ItemDecoration可以自定义Item之间的分隔线
- 多类型布局:支持多种Item布局类型,非常适合复杂的列表展示
2.3 RecyclerView的核心组件
- RecyclerView:视图容器,负责显示列表
- Adapter:适配器,负责数据与视图的绑定
- LayoutManager:布局管理器,负责Item的布局方式
- ViewHolder:视图持有者,用于缓存Item视图
- ItemDecoration:Item装饰,用于绘制Item之间的分隔线等
- ItemAnimator:Item动画,负责Item添加、删除、移动时的动画效果
3. 项目核心代码分析
3.1 NewsBean.java - 新闻数据模型
3.1.1 代码结构
3.1.2 字段详解
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | int | 新闻唯一标识符,用于区分不同的新闻条目 |
| title | String | 新闻标题,显示在新闻条目的主要位置 |
| imgList | List<Integer> | 新闻图片列表,存储图片资源的ID,支持多张图片 |
| name | String | 新闻发布者的名称或来源 |
| comment | String | 评论数量,格式如"9884评" |
| time | String | 新闻发布时间,格式如"6小时前"、"刚刚" |
| type | int | 新闻类型,1表示单图或置顶新闻,2表示三图新闻 |
3.1.3 设计思想
NewsBean采用了经典的Java Bean设计模式:
- 私有化字段:所有字段都使用private修饰符,保证数据封装性
- 提供getter/setter方法:通过公共方法访问和修改字段
- 无参构造函数:虽然代码中没有显式定义,但Java会默认提供
- 支持多种图片:使用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 数据准备说明
3.2.3 onCreate方法 - 初始化界面
详细步骤解析:
- 调用父类onCreate:
super.onCreate(savedInstanceState)- 必须调用,确保Activity正确初始化 - 加载布局:
setContentView(R.layout.activity_main)- 加载主界面布局文件 - 准备数据:
setData()- 调用自定义方法,准备新闻数据 - 查找RecyclerView:
mRecyclerView = findViewById(R.id.rv_list)- 通过ID找到RecyclerView控件 - 设置布局管理器:
mRecyclerView.setLayoutManager(new LinearLayoutManager(this))- 设置为线性布局管理器 - 创建适配器:
mAdapter = new NewsAdapter(MainActivity.this, NewsList)- 创建适配器实例 - 设置适配器:
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);
}
}
详细解析:
- 初始化列表:
NewsList = new ArrayList<NewsBean>()- 创建ArrayList用于存储新闻数据 - 循环组装数据:使用for循环遍历titles数组,逐个创建NewsBean对象
- 设置基本属性:设置id、title、name、comment、time、type等基本属性
- 设置图片列表:使用switch语句根据不同的位置设置不同的图片列表
- 添加到列表:将组装好的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;
}
设计要点:
- 继承RecyclerView.Adapter:这是RecyclerView适配器的基类
- 泛型参数:使用RecyclerView.ViewHolder作为泛型参数,因为我们有两种ViewHolder类型
- 构造函数:接收Context和数据列表作为参数
- 成员变量:保存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;
}
详细解析:
-
LayoutInflater的使用:
- LayoutInflater用于将XML布局文件转换为View对象
inflate()方法的三个参数:- 第一个参数:布局资源ID
- 第二个参数:父ViewGroup
- 第三个参数:是否将生成的View添加到父ViewGroup中(这里传false,因为RecyclerView会自动添加)
-
根据viewType创建不同的ViewHolder:
- 当viewType == 1时,加载list_item_one.xml布局,创建MyViewHolder1
- 当viewType == 2时,加载list_item_two.xml布局,创建MyViewHolder2
-
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));
}
}
详细解析:
-
获取数据:
NewsBean bean = NewsList.get(position)- 获取当前位置的数据对象 -
处理MyViewHolder1(单图/置顶新闻):
- 判断是否为置顶新闻:
- 如果position == 0,显示置顶标识(iv_top),隐藏图片(iv_img)
- 否则,隐藏置顶标识,显示图片
- 设置文本数据:设置标题、来源、评论、时间
- 设置图片:如果图片列表不为空,设置第一张图片
- 判断是否为置顶新闻:
-
处理MyViewHolder2(三图新闻):
- 设置文本数据:设置标题、来源、评论、时间
- 设置图片:依次设置三张图片
-
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_top | ImageView | 置顶标识图片 |
| iv_img | ImageView | 新闻图片 |
| title | TextView | 新闻标题 |
| name | TextView | 新闻来源 |
| comment | TextView | 评论数量 |
| time | TextView | 发布时间 |
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_img1 | ImageView | 第一张新闻图片 |
| iv_img2 | ImageView | 第二张新闻图片 |
| iv_img3 | ImageView | 第三张新闻图片 |
| title | TextView | 新闻标题 |
| name | TextView | 新闻来源 |
| comment | TextView | 评论数量 |
| time | TextView | 发布时间 |
4. 布局资源详解
4.1 activity_main.xml - 主界面布局
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_width | match_parent | 宽度匹配父容器(填满整个屏幕宽度) |
| layout_height | match_parent | 高度匹配父容器(填满整个屏幕高度) |
| background | @color/light_gray_color | 背景颜色,使用颜色资源引用 |
| orientation | vertical | 子元素垂直排列 |
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_width | match_parent | 宽度填满屏幕 |
| layout_height | 40dp | 高度固定为40dp |
| background | @android:color/white | 背景颜色为白色,使用系统颜色资源 |
| orientation | horizontal | 子元素水平排列 |
标签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_width | match_parent | 宽度填满屏幕 |
| layout_height | match_parent | 高度填满剩余空间 |
4.2 title_bar.xml - 标题栏布局
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_width | match_parent | 宽度填满屏幕 |
| layout_height | 50dp | 高度固定为50dp |
| background | #d33d3c | 背景颜色为红色(今日头条的主题色) |
| orientation | horizontal | 子元素水平排列 |
| paddingLeft | 10dp | 左边内边距 |
| paddingRight | 10dp | 右边内边距 |
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_width | wrap_content | 宽度自适应内容 |
| layout_height | wrap_content | 高度自适应内容 |
| layout_gravity | center | 在父容器中居中 |
| text | 仿今日头条 | 显示的文本 |
| textColor | @android:color/white | 文字颜色为白色 |
| textSize | 22sp | 文字大小为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_width | match_parent | 宽度填满剩余空间 |
| layout_height | 35dp | 高度固定为35dp |
| layout_gravity | center_vertical | 垂直居中 |
| layout_marginStart | 15dp | 起始边距(兼容RTL布局) |
| layout_marginLeft | 5dp | 左边距 |
| layout_marginRight | 15dp | 右边距 |
| background | @drawable/search_bg | 背景使用search_bg drawable资源 |
| gravity | center_vertical | 文字垂直居中 |
| textColor | @android:color/black | 输入文字颜色为黑色 |
| hint | 搜你想搜的 | 提示文字 |
| textColorHint | @color/gray_color | 提示文字颜色为灰色 |
| textSize | 14sp | 文字大小为14sp |
| paddingLeft | 30dp | 左边内边距(为搜索图标预留空间) |
4.3 list_item_one.xml - 单图/置顶新闻Item布局
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_width | match_parent | 宽度填满屏幕 |
| layout_height | 90dp | 高度固定为90dp |
| layout_marginBottom | 8dp | 底部外边距,与下一个Item保持距离 |
| background | @android:color/white | 背景颜色为白色 |
| padding | 8dp | 四周内边距 |
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_width | wrap_content | 宽度自适应内容 |
| layout_height | wrap_content | 高度自适应内容 |
| orientation | vertical | 子元素垂直排列 |
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_width | 280dp | 宽度固定为280dp |
| layout_height | wrap_content | 高度自适应内容 |
| maxLines | 2 | 最多显示2行,超出部分省略 |
| textColor | #3c3c3c | 文字颜色为深灰色 |
| textSize | 16sp | 文字大小为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_width | 20dp | 宽度固定为20dp |
| layout_height | 20dp | 高度固定为20dp |
| layout_alignParentBottom | true | 与父容器底部对齐 |
| 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_width | match_parent | 宽度填满剩余空间 |
| layout_height | wrap_content | 高度自适应内容 |
| layout_alignParentBottom | true | 与父容器底部对齐 |
| layout_toRightOf | @id/iv_top | 在iv_top的右边 |
| orientation | horizontal | 子元素水平排列 |
三个信息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_width | match_parent | 宽度填满剩余空间 |
| layout_height | 90dp | 高度固定为90dp |
| layout_toRightOf | @id/ll_info | 在ll_info的右边 |
| padding | 3dp | 四周内边距 |
4.4 list_item_two.xml - 三图新闻Item布局
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_width | match_parent | 宽度填满屏幕 |
| layout_height | wrap_content | 高度自适应内容(因为有三张图片,高度不固定) |
| layout_marginBottom | 8dp | 底部外边距 |
| 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_width | match_parent | 宽度填满屏幕 |
| layout_height | wrap_content | 高度自适应内容 |
| maxLines | 2 | 最多显示2行 |
| padding | 8dp | 四周内边距 |
| textColor | #3c3c3c | 文字颜色为深灰色 |
| textSize | 16sp | 文字大小为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_width | match_parent | 宽度填满屏幕 |
| layout_height | wrap_content | 高度自适应内容 |
| layout_below | @id/tv_title | 在tv_title的下方 |
| orientation | horizontal | 子元素水平排列 |
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 项目技术要点回顾
-
RecyclerView的使用:
- 使用RecyclerView展示新闻列表
- 实现了多类型Item布局(单图/置顶、三图)
- 使用LinearLayoutManager实现线性布局
-
Adapter的设计:
- 继承RecyclerView.Adapter
- 重写getItemViewType方法支持多类型
- 实现两个ViewHolder类
- 在onBindViewHolder中根据ViewHolder类型绑定数据
-
ViewHolder模式:
- 强制使用ViewHolder
- 在ViewHolder构造函数中findViewById
- 避免了重复的findViewById操作,提高性能
-
布局文件的设计:
- 使用include标签复用标题栏
- 使用style标签统一样式
- 合理使用LinearLayout和RelativeLayout
- 使用shape drawable作为背景
-
数据模型的设计:
- 使用Java Bean模式
- 使用List存储多张图片
- 使用type字段区分Item类型
6.2 RecyclerView的工作原理
-
初始化阶段:
- 创建RecyclerView实例
- 设置LayoutManager
- 创建Adapter实例
- 设置Adapter
-
布局阶段:
- LayoutManager测量和布局Item
- 根据屏幕大小决定显示多少个Item
- 创建足够的ViewHolder
-
滚动阶段:
- 当Item滚出屏幕时,ViewHolder被回收
- 当新的Item要进入屏幕时,使用回收的ViewHolder
- 调用onBindViewHolder重新绑定数据
-
回收机制:
- RecyclerView维护一个回收池
- 回收的ViewHolder可以被重复利用
- 大大减少了创建对象的开销
6.3 项目的可扩展性
-
添加更多Item类型:
- 在NewsBean中添加新的type值
- 创建新的ViewHolder类
- 创建新的布局文件
- 在Adapter中添加对应的逻辑
-
添加网络请求:
- 使用OkHttp或Retrofit
- 从服务器获取新闻数据
- 解析JSON数据
- 更新NewsList并调用notifyDataSetChanged
-
添加下拉刷新和上拉加载:
- 使用SwipeRefreshLayout
- 监听RecyclerView的滚动事件
- 实现加载更多逻辑
-
添加Item点击事件:
- 在Adapter中定义接口
- 在Activity中实现接口
- 在onBindViewHolder中设置点击事件
-
添加Item动画:
- 使用默认的ItemAnimator
- 自定义ItemAnimator
- 实现添加、删除、移动动画
6.4 最佳实践
-
使用ViewHolder:
- 必须使用ViewHolder模式
- 在ViewHolder中缓存所有View引用
-
避免在onBindViewHolder中做耗时操作:
- 不要在onBindViewHolder中加载图片
- 使用图片加载库(如Glide、Picasso)
- 提前准备好数据
-
使用DiffUtil:
- 当数据变化时,使用DiffUtil计算差异
- 只更新变化的Item
- 提高性能和用户体验
-
合理使用布局管理器:
- LinearLayoutManager:线性列表
- GridLayoutManager:网格布局
- StaggeredGridLayoutManager:瀑布流
-
使用ItemDecoration:
- 自定义Item之间的分隔线
- 可以设置不同的间距
- 可以绘制装饰