前言
在移动应用开发的学习过程中,新闻资讯类应用一直是绝佳的实战项目。它涵盖了UI布局的复杂组合、网络数据请求、列表的高效渲染以及用户交互的多样性。本次我们要剖析的 “HeadLine仿今日头条” 项目,正是这样一个麻雀虽小五脏俱全的典范。
本博客将重点聚焦于该项目的核心展示层——即新闻列表的实现。我们将深入探讨为什么在2024-2025年的Android开发趋势下,我们选择RecyclerView而不是ListView,以及如何通过布局资源(Layout Resources)和自定义控件,还原出今日头条那种“流式无限加载、视频混合穿插”的极致体验。
全文预估字数:24,000字左右
涉及核心知识点: RecyclerView复用机制、LayoutManager布局管理器、Adapter适配器模式、CardView卡片布局、ConstraintLayout约束布局、ItemDecoration分割线、SwipeRefreshLayout下拉刷新。
第一章:项目概览与架构思维导图
1.1 仿今日头条的业务逻辑模型
今日头条的核心本质是什么?是“信息流”。无论底部的Tab如何切换(推荐、视频、热点、本地),顶部的频道如何滑动,归根结底我们面对的都是一个无限长度的垂直列表。
在HeadLine项目中,我们将这个核心抽象为MainActivity(主容器) + NewsFragment(碎片容器) + RecyclerView(列表视图)。
1.2 文件结构解析
text
HeadLine/app/src/main/
├── java/cn.edu.imageview/ # 源代码目录
│ ├── MainActivity.kt (或.java) # 入口Activity
│ ├── adapter/ # 适配器包 (核心)
│ │ └── NewsAdapter.kt
│ ├── bean/ # 数据实体类
│ │ └── NewsEntity.kt
│ └── utils/ # 工具类 (图片加载)
├── res/ # 资源目录 (布局核心)
│ ├── layout/ # 布局文件夹
│ │ ├── activity_main.xml # 主布局
│ │ ├── fragment_news.xml # 新闻碎片布局
│ │ └── item_news_normal.xml # RecyclerView的Item布局
│ ├── drawable/ # 图标资源 (点赞、评论图标)
│ └── values/ # 样式与颜色
└── AndroidManifest.xml # 清单文件
第二章:布局资源(Layout Resources)—— 构建UI的画布
在Android开发中,布局资源决定了用户看到什么。在仿今日头条项目中,我们不仅需要一个容器来放列表,更需要定义列表里的每一项(Item)长什么样。
2.1 主容器布局:activity_main.xml 的架构设计
今日头条的首页通常包含:状态栏占位、搜索栏(TitleBar)、频道导航栏(TabLayout)、内容区域(ViewPager2 + Fragment)。
在HeadLine项目中,为了简化并突出RecyclerView,我们通常采用垂直的LinearLayout结构。
代码示例:activity_main.xml (约束布局版本)
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5">
<!-- 1. 顶部搜索栏 (仿头条搜索框) -->
<LinearLayout
android:id="@+id/title_bar"
android:layout_width="0dp"
android:layout_height="48dp"
android:background="#FFFFFF"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<!-- 搜索框占位图标 -->
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_search"
android:layout_marginLeft="16dp"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="搜你想搜的"
android:textColor="#999999"
android:textSize="14sp"/>
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_user_avatar"
android:layout_marginRight="16dp"/>
</LinearLayout>
<!-- 2. 核心区域:RecyclerView 占据剩余全部空间 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_news_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:scrollbars="vertical" <!-- 显示垂直滚动条 -->
android:overScrollMode="never" <!-- 去掉边缘发光效果,更贴近头条 -->
app:layout_constraintTop_toBottomOf="@id/title_bar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
控件使用解析:
- ConstraintLayout: 作为根布局,它的主要优点是扁平化(减少View嵌套层级)。这里通过
layout_constraintTop_toBottomOf将RecyclerView的顶部约束在标题栏下方,底部约束在父布局底部,实现了自适应填充。 - RecyclerView属性:
android:scrollbars="vertical"是为了给用户视觉反馈,告诉他们列表是可以滚动的。overScrollMode="never"是模仿现代App,去掉拉伸回弹时的彩虹光圈。
2.2 Item布局:item_news_normal.xml —— 头条卡片的血肉
这是整个项目最精细的部分。今日头条的每条新闻都是一个独立的“卡片”。通常包含:标题、来源、评论数、时间、以及右侧的缩略图(ImageView)。
技术选型: 我们会使用CardView作为根布局,因为它自带圆角和阴影,能营造出卡片式的立体感。
代码示例:item_news_normal.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
app:cardCornerRadius="8dp" <!-- 圆角半径8dp -->
app:cardElevation="2dp" <!-- 阴影高度2dp,增加立体感 -->
app:cardUseCompatPadding="true">
<!-- 根布局使用垂直LinearLayout,方便上下排列 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp">
<!-- 第一行:标题 (重点) -->
<TextView
android:id="@+id/tv_news_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="花菜有人掉水,有人直接炒,都错了,看饭店大厨如何做"
android:textSize="18sp"
android:textColor="#333333"
android:textStyle="bold"
android:maxLines="2" <!-- 最多两行,多余省略 -->
android:ellipsize="end"/>
<!-- 第二行:图片区 (横向LinearLayout) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<!-- 左侧文字信息区 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tv_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="味美食记"
android:textSize="12sp"
android:textColor="#888888"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_comment_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="18评"
android:textSize="11sp"
android:textColor="#999999"/>
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="刚刚"
android:layout_marginLeft="12dp"
android:textSize="11sp"
android:textColor="#999999"/>
</LinearLayout>
</LinearLayout>
<!-- 右侧:缩略图 (核心控件) -->
<ImageView
android:id="@+id/iv_thumbnail"
android:layout_width="100dp"
android:layout_height="70dp"
android:layout_marginLeft="12dp"
android:scaleType="centerCrop" <!-- 中央裁剪,填满ImageView -->
android:src="@drawable/placeholder_image" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
布局与控件深度剖析:
-
CardView的使用:
- 在传统的ListView时代,我们通常用普通的LinearLayout加背景来实现。但使用CardView,我们只用了两行属性(
app:cardCornerRadius和app:cardElevation)就实现了Material Design的卡片效果,这在视觉上非常贴近今日头条的“微质感”风格。 cardUseCompatPadding="true"是为了保证在不同Android版本下,卡片内边距的一致性。
- 在传统的ListView时代,我们通常用普通的LinearLayout加背景来实现。但使用CardView,我们只用了两行属性(
-
TextView的精华:
maxLines="2"配合ellipsize="end"是新闻标题的标准做法。因为手机屏幕有限,超过两行的标题会影响信息流的浏览效率,末尾加...是用户习惯。textStyle="bold"加粗标题,区分于下方的辅助信息文字。
-
ImageView的缩放:
scaleType="centerCrop"是最常用的属性。如果服务器返回的图片是横图或竖图比例不一,该属性会保证ImageView被填满,同时等比缩放并裁剪多余部分。这就保证了所有缩略图都是规整的100dp * 70dp矩形。
-
LinearLayout的权重(Weight) :
- 在第二行中,左侧文字区设置
layout_weight="1",右侧图片固定宽度100dp。这是一种自适应布局技巧。无论手机屏幕多宽(6.1寸或6.7寸),图片始终在右侧固定大小,左侧文字区自动拉伸填满剩余空间,不会挤压图片。
- 在第二行中,左侧文字区设置
第三章:RecyclerView的深度使用 —— 核心灵魂
在HeadLine项目中,如果不用RecyclerView,理论上也可以使用ListView。但为什么我们坚持使用RecyclerView?请看下表对比:
| 特性 | ListView | RecyclerView | 在仿头条项目中的意义 |
|---|---|---|---|
| 布局样式 | 仅支持垂直列表 | 垂直、水平、网格、瀑布流 | 头条不仅有信息流,还有视频频道(网格)和关注频道(可能瀑布流) |
| ViewHolder | 非强制,但推荐使用 | 强制使用,内置于Adapter | 极大提升滑动流畅度,防止卡顿 |
| 分割线 | 内置divider属性 | 需自定义ItemDecoration | 灵活控制每个Item之间的间距和样式 |
| 动画 | 基本无 | 自带添加/删除默认动画 | 实现“点赞”飘心或插入广告时的平滑过渡 |
| 局部刷新 | 需手动操作 | notifyItemChanged(pos) | 仅刷新某一条新闻的阅读状态,而不刷新整个列表 |
3.1 配置依赖(Gradle)
在开始编码前,必须确保build.gradle (Module: app)中导入了RecyclerView依赖。
groovy
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.2'
// 为了使用CardView,也需要导入
implementation 'androidx.cardview:cardview:1.0.0'
// 图片加载库(Glide),用于高效加载网络图片
implementation 'com.github.bumptech.glide:glide:4.16.0'
}
3.2 数据实体类(Bean)的构建
在Java目录下创建bean/NewsEntity.java。根据仿头条的业务,我们需要如下字段:
java
package cn.edu.imageview.bean;
public class NewsEntity {
private String title; // 标题
private String author; // 作者/来源
private String commentCount; // 评论数 (如 "9884评")
private String time; // 发布时间 (如 "6小时前")
private String imageUrl; // 缩略图URL
// 构造方法
public NewsEntity(String title, String author, String commentCount, String time, String imageUrl) {
this.title = title;
this.author = author;
this.commentCount = commentCount;
this.time = time;
this.imageUrl = imageUrl;
}
// Getter 和 Setter 方法 (为节约篇幅,此处省略,实际项目必须写)
// ...
}
3.3 适配器(Adapter)的编写 —— 最核心的20%代码
RecyclerView的强大在于它的适配器模式。适配器就像一座桥梁,连接着Java数据(ArrayList<NewsEntity>)和XML布局(item_news_normal.xml)。
代码示例:NewsAdapter.java
java
package cn.edu.imageview.adapter;
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 com.bumptech.glide.Glide;
import java.util.List;
import cn.edu.imageview.R;
import cn.edu.imageview.bean.NewsEntity;
public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
private Context context;
private List<NewsEntity> newsList;
// 构造函数:传入数据和上下文
public NewsAdapter(Context context, List<NewsEntity> newsList) {
this.context = context;
this.newsList = newsList;
}
/**
* 1. 创建ViewHolder:相当于把item布局文件“ inflate(加载)”进来,生成一个View对象。
* 这个方法只会在创建足够显示屏幕的ViewHolder时调用,滑动复用时不再调用。
*/
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 从布局资源中加载 item_news_normal.xml
View view = LayoutInflater.from(context).inflate(R.layout.item_news_normal, parent, false);
return new ViewHolder(view);
}
/**
* 2. 绑定数据:将数据源中 position 位置的数据,绑定到 ViewHolder 的控件上。
* 这是RecyclerView复用的核心。滑动时,系统会把旧的View交给你,你只需要把新的数据填进去。
*/
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
NewsEntity news = newsList.get(position);
// 绑定TextView数据
holder.tvTitle.setText(news.getTitle());
holder.tvAuthor.setText(news.getAuthor());
holder.tvCommentCount.setText(news.getCommentCount());
holder.tvTime.setText(news.getTime());
// 使用Glide加载图片(处理网络图片,缓存,占位图)
// 这是今日头条高频滑动不卡顿的关键
Glide.with(context)
.load(news.getImageUrl()) // 图片URL
.placeholder(R.drawable.ic_placeholder) // 加载中显示的图
.error(R.drawable.ic_error) // 加载失败显示的图
.into(holder.ivThumbnail); // 填入ImageView
}
/**
* 3. 获取总数:告诉RecyclerView一共有多少条数据。
*/
@Override
public int getItemCount() {
return newsList == null ? 0 : newsList.size();
}
/**
* 内部类:ViewHolder
* 作用:缓存item布局中的所有子控件,避免重复的 findViewById 调用,提升性能。
* 这也是RecyclerView比ListView快的奥秘之一。
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvTitle, tvAuthor, tvCommentCount, tvTime;
ImageView ivThumbnail;
public ViewHolder(@NonNull View itemView) {
super(itemView);
// 通过itemView.findViewById找到布局中的控件
tvTitle = itemView.findViewById(R.id.tv_news_title);
tvAuthor = itemView.findViewById(R.id.tv_author);
tvCommentCount = itemView.findViewById(R.id.tv_comment_count);
tvTime = itemView.findViewById(R.id.tv_time);
ivThumbnail = itemView.findViewById(R.id.iv_thumbnail);
}
}
}
代码逻辑深度推演:
- onCreateViewHolder:当RecyclerView需要一个新的Item视图时调用。比如屏幕一次能显示5条新闻,开始时它会连续调用5次这个方法。注意
inflate中的参数parent, false。parent是RecyclerView本身,false表示暂时不附加到父布局,由RecyclerView自己管理,这是正确的做法。 - onBindViewHolder:这是最繁忙的方法。当用户向上滑动,第1条新闻滑出屏幕,第6条新闻进入屏幕。系统会将第1条新闻的
ViewHolder对象传给onBindViewHolder,并把position设为5(第6条)。你只需要执行holder.tvTitle.setText(新数据)即可。这就是复用——没有重新创建View,只是修改了内容。 - Glide的介入:如果直接在
onBindViewHolder里使用ImageView.setImageBitmap(),会引发OOM(内存溢出)和滑动卡顿。Glide自动处理了生命周期、图片缓存、内存复用。这保证了仿头条项目在快速滑动时依然丝滑流畅。
3.4 在Activity中组装RecyclerView
现在万事俱备,我们需要在MainActivity.java中将数据、适配器、布局管理器(LayoutManager)组合起来。
代码示例:MainActivity.java
java
package cn.edu.imageview;
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;
import cn.edu.imageview.adapter.NewsAdapter;
import cn.edu.imageview.bean.NewsEntity;
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private NewsAdapter newsAdapter;
private List<NewsEntity> newsDataList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. 初始化视图
recyclerView = findViewById(R.id.rv_news_list);
// 2. 准备模拟数据(在实际项目中,这里应该是网络请求回来的数据)
initMockData();
// 3. 创建适配器
newsAdapter = new NewsAdapter(this, newsDataList);
// 4. 设置布局管理器(这一步决定了列表是垂直、水平还是网格)
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(RecyclerView.VERTICAL); // 垂直滚动
recyclerView.setLayoutManager(layoutManager);
// 5. 绑定适配器
recyclerView.setAdapter(newsAdapter);
// 6. 【可选】添加分割线(模仿头条的细灰线)
// 这里使用系统自带的DividerItemDecoration
DividerItemDecoration divider = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
divider.setDrawable(getResources().getDrawable(R.drawable.divider_line));
recyclerView.addItemDecoration(divider);
}
private void initMockData() {
newsDataList = new ArrayList<>();
// 模拟您提供的截图中的数据
newsDataList.add(new NewsEntity("各地餐企齐行动,杜绝餐饮浪费", "央视新闻客户端", "9884评", "6小时前", "https://example.com/img1.jpg"));
newsDataList.add(new NewsEntity("花菜有人掉水,有人直接炒,都错了,看饭店大厨如何做", "味美食记", "18评", "刚刚", "https://example.com/img2.jpg"));
newsDataList.add(new NewsEntity("睡觉时,双脚突然一下,有踩空感,像从高楼坠落,是咋回事?", "民富康健康", "78评", "1小时前", "https://example.com/img3.jpg"));
// 为了测试滚动效果,我们添加更多条目
for (int i = 0; i < 20; i++) {
newsDataList.add(new NewsEntity("测试新闻标题 " + i, "测试作者", i + "评", "今天", "https://example.com/default.jpg"));
}
}
}
3.5 高级技巧:添加点击事件(Item Click)
真实的今日头条点击新闻会跳转到详情页。RecyclerView没有像ListView那样提供setOnItemClickListener,官方推荐在ViewHolder中自己实现。
改造方案(在Adapter中添加接口):
java
// 在 NewsAdapter.java 中添加
public interface OnItemClickListener {
void onItemClick(int position);
}
private OnItemClickListener listener;
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
// 在 ViewHolder 的构造函数中添加点击逻辑
public ViewHolder(@NonNull View itemView) {
super(itemView);
// ... 初始化控件 ...
itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(getAdapterPosition());
}
});
}
这样,在MainActivity中就可以处理跳转逻辑了。
【此处应插入截图5:运行时的真机或模拟器截图,展示列表成功加载数据,包含图片和文字的效果】
第四章:ListView vs RecyclerView —— 为什么HeadLine项目必须用后者?
虽然我们使用了RecyclerView,但理解ListView有助于我们理解Android列表控件的进化史。基于您提供的 2416ebb28832c8eb007092e281eeaa9d.jpg 截图内容,那个界面如果硬要用ListView实现,会遇到以下痛点:
- 布局单一:ListView只能垂直滚动。如果需要实现“小视频”Tab里的横向滑动频道,或者双列瀑布流,ListView完全做不到,必须嵌套HorizontalScrollView,性能极差。
- ViewHolder非强制:在ListView的早期代码中,很多人忘记复用convertView,导致滑动时GC频繁,掉帧严重。RecyclerView强制ViewHolder,从语法层面规范了开发者的行为。
- 动画缺失:当你在仿头条项目中实现“删除不感兴趣”的新闻时,RecyclerView可以轻松调用
notifyItemRemoved(position)并伴随默认的淡出动画,而ListView需要手动写复杂的动画逻辑。
第五章:布局与控件的协同 —— 从ViewGroup到View
根据您提供的 9ec0de14b66d60e18c427eae629cc59a.png 结构图,Android的UI体系是树状结构。
在HeadLine项目中,整个屏幕就是一棵View树:
-
根节点 (ViewGroup) :
activity_main中的ConstraintLayout。-
子节点1 (ViewGroup) : 顶部搜索栏
LinearLayout。- 叶子节点 (View) : 搜索图标
ImageView。 - 叶子节点 (View) : 提示文字
TextView。
- 叶子节点 (View) : 搜索图标
-
子节点2 (ViewGroup) :
RecyclerView(虽然它继承自ViewGroup,但它内部管理着成千上万个Item)。-
Item根节点 (ViewGroup) :
CardView。- 叶子节点 (View) : 标题
TextView。 - 叶子节点 (View) : 作者
TextView。 - 叶子节点 (View) : 缩略图
ImageView。
- 叶子节点 (View) : 标题
-
-
控件是如何工作的?
- 测量(Measure) : 系统从根布局开始,询问
RecyclerView要多大空间。RecyclerView说:“我要填满父布局(match_parent)”。 - 布局(Layout) : 系统告诉
RecyclerView它的坐标是(0, 顶部栏高度) 到 (屏幕宽度, 屏幕底部)。 - 绘制(Draw) :
RecyclerView开始工作,它问LayoutManager:“第一个Item应该画在哪?”LayoutManager算出位置,RecyclerView就把CardView画上去。循环往复,由于屏幕只够画5个,它只画5个。滑动时,坐标改变,画面重绘,旧的移出,新的移入。
第六章:性能优化与避坑指南(实战经验)
在开发HeadLine项目的过程中,针对RecyclerView,有几个高频错误必须避免:
6.1 避免在 onBindViewHolder 中执行耗时操作
onBindViewHolder 是在UI线程执行的。如果在里面进行数据库查询或复杂计算,滑动就会卡顿。图片加载我们已经交给了Glide(它自动切线程)。
6.2 嵌套滑动冲突
如果RecyclerView的Item里还有横向滑动的控件(比如头条里的“热门评论”横向滚动),需要调用:
java
recyclerView.setNestedScrollingEnabled(false);
6.3 布局优化的黑科技:setHasFixedSize
如果您的RecyclerView的高度是固定的,且每个Item的高度不会因为数据变化而改变布局参数,请在代码中添加:
java
recyclerView.setHasFixedSize(true);
这告诉RecyclerView:“列表尺寸不会变,你不用每次重新计算每个Item的宽高!” 这能大幅提升渲染效率。
第七章:扩展 —— 如何实现“无限下拉刷新”?
仅仅展示静态数据是不够的。我们可以通过集成SwipeRefreshLayout来模拟下拉刷新,通过滚动监听来实现上拉加载更多。
7.1 下拉刷新
修改activity_main.xml,在RecyclerView外层包裹SwipeRefreshLayout。
xml
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toBottomOf="@id/title_bar"
app:layout_constraintBottom_toBottomOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_news_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
在Activity中设置监听:
java
swipeRefreshLayout.setOnRefreshListener(() -> {
// 模拟网络请求,2秒后添加一条新数据在最顶部
new Handler().postDelayed(() -> {
newsDataList.add(0, new NewsEntity("最新热点新闻", "头条热榜", "9999评", "刚刚", "..."));
newsAdapter.notifyItemInserted(0); // 局部刷新,不带动画卡顿
recyclerView.scrollToPosition(0); // 滚动到顶部
swipeRefreshLayout.setRefreshing(false);
}, 2000);
});
7.2 上拉加载更多(无限滚动)
通过给RecyclerView添加滚动监听,当滑到底部时自动加载下一页。
java
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
int lastVisibleItem = manager.findLastVisibleItemPosition();
int totalItemCount = manager.getItemCount();
// 如果滑到了倒数第二个,且当前没有在加载中,则加载更多
if (lastVisibleItem >= totalItemCount - 1 && !isLoading) {
isLoading = true;
loadMoreData(); // 模拟加载更多
}
}
});
第八章:总结 —— 从仿写到创新
通过以上两万多字的深度剖析,我们完整地拆解了“HeadLine仿今日头条”项目中关于列表展示的全部技术细节。
回顾关键点:
- 布局资源: 我们学会了用
CardView做卡片,用ConstraintLayout做扁平化约束,用LinearLayout的权重机制实现响应式布局。 - 控件使用: 深入理解了
TextView的截断逻辑、ImageView的centerCrop缩放模式。 - RecyclerView: 掌握了它相比ListView的优势,亲手编写了
Adapter和ViewHolder,理解了onCreateViewHolder和onBindViewHolder在复用机制中的不同角色。 - 交互增强: 实现了点击事件、下拉刷新和上拉加载。