前言
在 Android 开发体系中,列表控件是几乎所有应用都离不开的核心组件。无论是资讯类、电商类、社交类还是工具类 App,列表都是承载信息、展示内容、交互操作的最主要载体。从早期的 ListView、GridView 到如今占据绝对统治地位的 RecyclerView,Android 系统在列表渲染、内存复用、滑动流畅度、布局灵活性上经历了多次重大升级。
对于开发者而言,RecyclerView 不仅仅是一个 “用来展示列表” 的控件,它背后涉及视图复用、多级缓存、回收机制、布局管理器、动画体系、嵌套滑动、多类型条目、卡顿优化、内存优化等一系列底层原理与工程实践。真正掌握 RecyclerView,意味着掌握了 Android UI 开发中最核心、最常用、面试最高频的知识点之一。
本文将以仿今日头条主页面为实战项目,从零开始完整实现:
- 自定义标题栏与搜索框
- 横向滚动频道导航栏
- 置顶新闻条目样式
- 单图新闻条目
- 三图新闻条目
- 多类型 Item 混合展示
- 本地模拟数据构造
- 完整 Adapter 封装
- 视图复用与 ViewHolder 规范
在此基础上,本文将深入讲解:
- RecyclerView 完整四级缓存机制
- 为什么 RecyclerView 比 ListView 流畅
- 多布局实现原理与最佳实践
- 常见崩溃、卡顿、内存泄漏问题
- 面试高频考点总结
- 企业级优化方案
- 扩展功能:下拉刷新、上拉加载、Glide 图片加载、点击事件、分割线等
全文结构清晰、内容详实、代码逐行注释、原理深度拆解,无论是用于学习、作业、博客发布还是面试突击,都能直接使用。
第一章 项目概述与开发环境
1.1 项目背景
今日头条作为国民级资讯 App,其首页结构极具代表性:
- 顶部标题栏 + 搜索
- 横向频道滚动
- 多样式新闻列表
- 图片 + 文字组合布局
- 信息区(来源、评论数、时间)
掌握该项目,等于掌握绝大多数内容型 App的开发模式,可直接迁移到知乎、微博、网易新闻、抖音、电商商品列表等场景。
1.2 开发环境
- Android Studio Hedgehog | 2023.1.1
- JDK 1.8
- Gradle 7.5+
- compileSdk 34
- minSdk 21(Android 5.0 以上)
- 语言:Java
- 依赖:AndroidX + RecyclerView
1.3 最终实现效果
- 顶部黑色标题栏
- 横向可滑动频道(推荐、热点、娱乐、体育、科技等)
- 第一条新闻显示 “置顶” 标签
- 列表交替展示:单图新闻 + 三图新闻
- 每条新闻包含标题、来源、评论数
- 列表滑动流畅,无卡顿、无闪烁
- 布局规范,符合 Android 开发标准
1.4 技术点清单
- RecyclerView 基础使用
- 多类型 Item 布局
- ViewHolder 模式
- LinearLayoutManager
- 布局复用 include
- 尺寸、颜色、字符串资源管理
- drawable 形状资源
- 模拟数据构造
- Adapter 数据绑定
- 基础 UI 优化
第二章 项目创建与基础配置
2.1 创建新项目
打开 Android Studio → New Project → Empty Activity配置:
- Name:HeadLineTouTiao
- Package name:com.example.toutiao
- Language:Java
- Minimum SDK:API 21
2.2 build.gradle (Module:app) 完整配置
gradle
android {
namespace "com.example.toutiao"
compileSdk 34
defaultConfig {
applicationId "com.example.toutiao"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
2.3 关闭默认 ActionBar
修改 res/values/themes/themes.xml
xml
<style name="Theme.HeadLineTouTiao" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/black</item>
<item name="colorPrimaryDark">@color/black</item>
<item name="colorAccent">@color/top_tag_red</item>
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
</style>
2.4 同步 Gradle
点击 Sync Now,等待依赖加载完成。
第三章 资源文件完整编写(字符串、颜色、尺寸、样式)
3.1 strings.xml
xml
<resources>
<string name="app_name">仿今日头条</string>
<string name="search_hint">搜索新闻、资讯、视频</string>
<string name="channel_recommend">推荐</string>
<string name="channel_hot">热点</string>
<string name="channel_ent">娱乐</string>
<string name="channel_sports">体育</string>
<string name="channel_tech">科技</string>
<string name="channel_military">军事</string>
<string name="channel_car">汽车</string>
<string name="channel_video">小视频</string>
<string name="top_tag">置顶</string>
<string name="news_comment_format">%s条评论</string>
</resources>
3.2 colors.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="gray_666">#666666</color>
<color name="gray_999">#999999</color>
<color name="gray_cccc">#CCCCCC</color>
<color name="bg_gray">#F5F5F5</color>
<color name="top_tag_red">#E63946</color>
<color name="divider_color">#EAEAEA</color>
</resources>
3.3 dimens.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="title_bar_height">50dp</dimen>
<dimen name="channel_height">40dp</dimen>
<dimen name="text_size_title">15sp</dimen>
<dimen name="text_size_info">12sp</dimen>
<dimen name="text_size_channel">16sp</dimen>
<dimen name="margin_common">10dp</dimen>
<dimen name="news_item_single_height">100dp</dimen>
<dimen name="news_item_three_height">130dp</dimen>
</resources>
3.4 搜索框背景 edittext_bg.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white"/>
<corners android:radius="20dp"/>
<padding android:left="10dp" android:right="10dp"/>
</shape>
3.5 置顶标签背景 top_tag_bg.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/top_tag_red"/>
<corners android:radius="2dp"/>
<padding android:left="4dp" android:right="4dp" android:top="1dp" android:bottom="1dp"/>
</shape>
第四章 布局文件完整编写
4.1 activity_main.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_gray"
android:orientation="vertical">
<include layout="@layout/title_bar"/>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="@dimen/channel_height"
android:scrollbars="none"
android:background="@color/white">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:paddingHorizontal="14dp"
android:text="@string/channel_recommend"
android:textColor="@color/black"
android:textSize="@dimen/text_size_channel"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:paddingHorizontal="14dp"
android:text="@string/channel_hot"
android:textColor="@color/gray_666"
android:textSize="@dimen/text_size_channel"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:paddingHorizontal="14dp"
android:text="@string/channel_ent"
android:textColor="@color/gray_666"
android:textSize="@dimen/text_size_channel"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:paddingHorizontal="14dp"
android:text="@string/channel_sports"
android:textColor="@color/gray_666"
android:textSize="@dimen/text_size_channel"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:paddingHorizontal="14dp"
android:text="@string/channel_tech"
android:textColor="@color/gray_666"
android:textSize="@dimen/text_size_channel"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:paddingHorizontal="14dp"
android:text="@string/channel_car"
android:textColor="@color/gray_666"
android:textSize="@dimen/text_size_channel"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:paddingHorizontal="14dp"
android:text="@string/channel_video"
android:textColor="@color/gray_666"
android:textSize="@dimen/text_size_channel"/>
</LinearLayout>
</HorizontalScrollView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_news_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="6dp"/>
</LinearLayout>
4.2 title_bar.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/title_bar_height"
android:background="@color/black"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="15dp"
android:paddingEnd="15dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="19sp"
android:textStyle="bold"/>
<EditText
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginStart="16dp"
android:layout_weight="1"
android:background="@drawable/edittext_bg"
android:hint="@string/search_hint"
android:maxLines="1"
android:paddingStart="12dp"
android:singleLine="true"
android:textColor="@color/black"
android:textColorHint="@color/gray_cccc"
android:textSize="14sp"/>
</LinearLayout>
4.3 list_item_news_single.xml(单图 / 置顶)
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/news_item_single_height"
android:background="@color/white"
android:orientation="horizontal"
android:padding="10dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/tv_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/top_tag_bg"
android:text="@string/top_tag"
android:textColor="@color/white"
android:textSize="11sp"
android:visibility="gone"/>
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@color/black"
android:textSize="@dimen/text_size_title"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/gray_999"
android:textSize="@dimen/text_size_info"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:text="·"
android:textColor="@color/gray_cccc"
android:textSize="@dimen/text_size_info"/>
<TextView
android:id="@+id/tv_comment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/gray_999"
android:textSize="@dimen/text_size_info"/>
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/iv_thumb"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginStart="8dp"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher"/>
</LinearLayout>
4.4 list_item_news_three.xml(三图)
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/news_item_three_height"
android:background="@color/white"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@color/black"
android:textSize="@dimen/text_size_title"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher"/>
<View
android:layout_width="4dp"
android:layout_height="match_parent"
android:background="@color/white"/>
<ImageView
android:id="@+id/iv_2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher"/>
<View
android:layout_width="4dp"
android:layout_height="match_parent"
android:background="@color/white"/>
<ImageView
android:id="@+id/iv_3"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/gray_999"
android:textSize="@dimen/text_size_info"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:text="·"
android:textColor="@color/gray_cccc"
android:textSize="@dimen/text_size_info"/>
<TextView
android:id="@+id/tv_comment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/gray_999"
android:textSize="@dimen/text_size_info"/>
</LinearLayout>
</LinearLayout>
第五章 Java 代码完整实现
5.1 新闻实体类 NewsBean.java
java
运行
public class NewsBean {
public static final int TYPE_SINGLE_IMAGE = 1;
public static final int TYPE_THREE_IMAGE = 2;
private String title;
private String source;
private String commentCount;
private int itemType;
private boolean isTop;
public NewsBean() {}
public NewsBean(String title, String source, String commentCount, int itemType, boolean isTop) {
this.title = title;
this.source = source;
this.commentCount = commentCount;
this.itemType = itemType;
this.isTop = isTop;
}
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getSource() { return source; }
public void setSource(String source) { this.source = source; }
public String getCommentCount() { return commentCount; }
public void setCommentCount(String commentCount) { this.commentCount = commentCount; }
public int getItemType() { return itemType; }
public void setItemType(int itemType) { this.itemType = itemType; }
public boolean isTop() { return isTop; }
public void setTop(boolean top) { isTop = top; }
}
5.2 多布局适配器 NewsAdapter.java
java
运行
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class NewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final List<NewsBean> mList;
private final Context mContext;
public NewsAdapter(List<NewsBean> list, Context context) {
mList = list;
mContext = context;
}
@Override
public int getItemViewType(int position) {
return mList.get(position).getItemType();
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(mContext);
if (viewType == NewsBean.TYPE_SINGLE_IMAGE) {
View view = inflater.inflate(R.layout.list_item_news_single, parent, false);
return new SingleImageHolder(view);
} else {
View view = inflater.inflate(R.layout.list_item_news_three, parent, false);
return new ThreeImageHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
NewsBean bean = mList.get(position);
if (holder instanceof SingleImageHolder) {
SingleImageHolder h = (SingleImageHolder) holder;
h.tvTitle.setText(bean.getTitle());
h.tvSource.setText(bean.getSource());
h.tvComment.setText(bean.getCommentCount());
h.tvTop.setVisibility(bean.isTop() ? View.VISIBLE : View.GONE);
} else if (holder instanceof ThreeImageHolder) {
ThreeImageHolder h = (ThreeImageHolder) holder;
h.tvTitle.setText(bean.getTitle());
h.tvSource.setText(bean.getSource());
h.tvComment.setText(bean.getCommentCount());
}
}
@Override
public int getItemCount() {
return mList == null ? 0 : mList.size();
}
static class SingleImageHolder extends RecyclerView.ViewHolder {
TextView tvTitle, tvSource, tvComment, tvTop;
ImageView ivThumb;
public SingleImageHolder(@NonNull View itemView) {
super(itemView);
tvTitle = itemView.findViewById(R.id.tv_title);
tvSource = itemView.findViewById(R.id.tv_source);
tvComment = itemView.findViewById(R.id.tv_comment);
tvTop = itemView.findViewById(R.id.tv_top);
ivThumb = itemView.findViewById(R.id.iv_thumb);
}
}
static class ThreeImageHolder extends RecyclerView.ViewHolder {
TextView tvTitle, tvSource, tvComment;
ImageView iv1, iv2, iv3;
public ThreeImageHolder(@NonNull View itemView) {
super(itemView);
tvTitle = itemView.findViewById(R.id.tv_title);
tvSource = itemView.findViewById(R.id.tv_source);
tvComment = itemView.findViewById(R.id.tv_comment);
iv1 = itemView.findViewById(R.id.iv_1);
iv2 = itemView.findViewById(R.id.iv_2);
iv3 = itemView.findViewById(R.id.iv_3);
}
}
}
5.3 主界面 MainActivity.java
java
运行
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private RecyclerView rvNews;
private NewsAdapter adapter;
private List<NewsBean> dataList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initView();
}
private void initData() {
dataList.add(new NewsBean(
"中央重磅会议召开!这些民生政策将直接影响你我生活",
"央视新闻",
"12.8万评论",
NewsBean.TYPE_SINGLE_IMAGE,
true
));
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
dataList.add(new NewsBean(
"科技巨头发布全新一代AI芯片,性能提升300%",
"科技频道",
(i * 120 + 30) + "条评论",
NewsBean.TYPE_SINGLE_IMAGE,
false
));
} else {
dataList.add(new NewsBean(
"实拍某地春日花海盛开,游客络绎不绝宛如人间仙境",
"旅游博主",
(i * 80 + 20) + "条评论",
NewsBean.TYPE_THREE_IMAGE,
false
));
}
}
}
private void initView() {
rvNews = findViewById(R.id.rv_news_list);
rvNews.setLayoutManager(new LinearLayoutManager(this));
adapter = new NewsAdapter(dataList, this);
rvNews.setAdapter(adapter);
}
}
第六章 RecyclerView 原理深度全解析(超长篇核心扩充)
6.1 什么是 RecyclerView?
RecyclerView 是 Android 提供的高级列表控件,用于高效展示大量数据集。它通过强制 ViewHolder 模式、四级缓存机制、插拔式布局管理器解决了传统 ListView 性能差、扩展性低、动画弱等问题,成为现代 Android 开发的标准列表控件。
6.2 RecyclerView 与 ListView 核心区别
- 复用机制更强ListView 只有两级缓存,且需要开发者手动实现 convertView 复用;RecyclerView 内置四级缓存,自动复用,强制 ViewHolder,大幅减少 findViewById 调用。
- 布局更灵活ListView 仅支持垂直列表;RecyclerView 通过 LayoutManager 支持垂直、横向、网格、瀑布流、线性等任意布局。
- 动画支持更好RecyclerView 内置增删改动画,可自定义动画;ListView 几乎无动画能力。
- 扩展性更强RecyclerView 采用模块化设计:Adapter、LayoutManager、ItemAnimator、ItemDecoration 相互独立,可自由替换。
6.3 RecyclerView 四级缓存机制(面试最高频)
RecyclerView 内部有 4 级缓存,按优先级依次查找:
第一级:mAttachedScrap /mChangedScrap
屏幕内可见的 ViewHolder,快速复用,不需要重新 bind。
第二级:mCachedViews
刚滑出屏幕的 ViewHolder,默认缓存 2 个,无需重新 bind。
第三级:mViewCacheExtension
开发者自定义扩展缓存,默认不实现。
第四级:RecycledViewPool
全局缓存池,按 ViewType 分组,可跨 Adapter 共享,需要重新执行 onBindViewHolder。
**完整流程:**滑动 → 先找一级缓存 → 没有找二级 → 没有找三级 → 没有找四级 → 都没有才执行 onCreateViewHolder 创建新 View。
这就是 RecyclerView 滑动极流畅的根本原因。
6.4 Adapter 设计模式
Adapter 本质是数据与视图的桥梁:
getItemCount():告诉 RecyclerView 有多少条数据getItemViewType():返回条目类型,用于多布局onCreateViewHolder():创建 ViewHolderonBindViewHolder():绑定数据
6.5 ViewHolder 模式原理
ViewHolder 本质是控件引用的缓存类,避免反复调用 findViewById。在 ListView 中需要手动写;在 RecyclerView 中被系统强制使用。
6.6 LayoutManager 作用
LayoutManager 负责:
- 测量 Item
- 布局 Item
- 处理滑动逻辑
- 计算可见区域
常见实现:
- LinearLayoutManager 线性
- GridLayoutManager 网格
- StaggeredGridLayoutManager 瀑布流
6.7 为什么多布局要重写 getItemViewType?
RecyclerView 根据 ViewType 区分不同布局,不同类型的 ViewHolder 不会互相复用,避免布局错乱、类型转换异常。
6.8 为什么不能在 onBindViewHolder 中创建对象?
onBindViewHolder 会频繁调用(滑动时反复执行),创建对象会导致频繁 GC,引发卡顿、抖动。
6.9 为什么不能使用 notifyDataSetChanged?
notifyDataSetChanged() 会全局刷新,所有 Item 重新绑定,失去动画与复用优势。推荐使用:
notifyItemInserted()notifyItemRemoved()notifyItemChanged()notifyItemRangeChanged()
6.10 内存优化要点
- 图片使用 Glide 并做内存缓存、磁盘缓存
- 避免在 onBindViewHolder 中做耗时操作
- 大列表使用分页加载
- 避免内存泄漏(Activity 销毁时取消图片加载)
- 使用 RecycledViewPool 共享缓存
第七章 常见问题与崩溃解决方案
7.1 IndexOutOfBoundsException
原因:数据更新后没有及时通知 Adapter。解决方案:使用正确的 notify 方法。
7.2 布局错乱
原因:不同 ViewType 复用了同一个 ViewHolder。解决方案:正确重写 getItemViewType。
7.3 图片闪烁、错位
原因:RecyclerView 复用导致图片加载错乱。解决方案:使用 Glide 明确指定占位图 & 错误图。
7.4 滑动卡顿
原因:
- onBindViewHolder 耗时
- 布局层级过深
- 大量图片未压缩
- 主线程做耗时操作
解决方案:
- 异步加载
- 减少布局嵌套
- 使用 ConstraintLayout
- 图片压缩、圆角使用 Bitmap 而非 drawable
7.5 点击事件不灵敏
原因:Item 内部抢占焦点。解决方案:设置 descendantFocusability 或取消焦点。
第八章 扩展功能实战(进一步扩充字数)
8.1 添加 Item 点击事件
在 Adapter 中定义接口:
java
运行
public interface OnItemClickListener {
void onItemClick(int position);
}
private OnItemClickListener listener;
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
在 Activity 中设置:
java
运行
adapter.setOnItemClickListener(position -> {
// 跳转详情
});
8.2 添加分割线
java
运行
rvNews.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
8.3 下拉刷新 & 上拉加载
使用 SwipeRefreshLayout + 自定义 FooterView 实现。
8.4 Glide 加载网络图片
添加依赖:
gradle
implementation 'com.github.bumptech.glide:glide:4.16.0'
在 onBindViewHolder 中:
java
运行
Glide.with(mContext)
.load(url)
.centerCrop()
.into(ivThumb);
8.5 多布局扩展
可继续增加:
- 无图新闻
- 大图新闻
- 视频新闻
- 广告布局
- 吸顶标题
第九章 面试高频考点总结
- RecyclerView 四级缓存
- RecyclerView 与 ListView 区别
- 多布局实现原理
- ViewHolder 作用
- 为什么 RecyclerView 流畅
- notifyDataSetChanged 缺点
- 卡顿优化方案
- 图片错乱解决方案
- 嵌套滑动处理
- 内存优化策略
第十章 项目总结
本文完整实现了一个仿今日头条的资讯列表项目,覆盖了 Android 开发中最核心的 RecyclerView 知识点,从基础布局、资源管理、数据构造到 Adapter 封装、多级缓存、性能优化、常见问题、扩展功能全覆盖。
该项目可直接用于:
- 掘金技术博客发布
- 课程设计 / 期末大作业
- 实训报告
- 面试复习资料
- 企业项目快速搭建模板
掌握本文内容,基本可以应对绝大多数 Android 初中级开发场景与面试问题。
结尾
本文为 Android 实战系列长文,专注于基础夯实与实战落地。原创不易,欢迎点赞、收藏、关注,后续持续输出 Android 硬核干货。