目录
一、为什么 RecyclerView 能取代 ListView?核心优势拆解
二、RecyclerView 基础入门:3 步实现一个标准列表
3. 第三步:实现 Adapter 与 Activity 绑定
(1)实现 Adapter(UserAdapter.java)
(2)在 Activity 中初始化 RecyclerView
三、RecyclerView 进阶技巧:覆盖 80% 开发场景
1. 优化数据更新:用 DiffUtil 替代 notifyDataSetChanged
(2)设置 RecyclerView 的 ItemViewCacheSize
4. 避免在 onBindViewHolder 中做耗时操作
(1)布局文件(activity_goods_list.xml)
(3)Adapter 实现(GoodsAdapter.java)
(4)Activity 实现(GoodsListActivity.java)
六、实战案例:订单列表(展开 / 收起 + 嵌套 RecyclerView)
(2)外层 Adapter(OrderAdapter.java)
(3)内层 Adapter(OrderGoodsAdapter.java)

class 卑微码农:
def __init__(self):
self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
self.发量 = 100 # 初始发量
self.咖啡因耐受度 = '极限'
def 修Bug(self, bug):
try:
# 试图用玄学解决问题
if bug.严重程度 == '离谱':
print("这一定是环境问题!")
else:
print("让我看看是谁又没写注释...哦,是我自己。")
except Exception as e:
# 如果try块都救不了,那就...
print("重启一下试试?")
self.发量 -= 1 # 每解决一个bug,头发-1
# 实例化一个我
我 = 卑微码农()
前言:
作为 Android 开发的 “列表之王”,RecyclerView 凭借灵活的布局、高效的性能,成为了几乎所有 App 的必备组件。不管是电商 App 的商品列表、社交 App 的消息流,还是工具类 App 的设置页面,都能看到它的身影。但很多开发者只停留在 “能用” 层面,对其缓存机制、性能优化、复杂场景适配一知半解。这篇文章从基础用法到进阶技巧,再到实战案例,用最通俗的语言 + 可直接运行的代码,带你彻底吃透 RecyclerView,让你在开发中少踩 90% 的坑!
一、为什么 RecyclerView 能取代 ListView?核心优势拆解

在 RecyclerView 出现之前,ListView 是 Android 列表展示的首选,但它的局限性越来越难满足复杂需求。RecyclerView 的诞生,正是为了解决这些痛点:
1. 布局灵活性:一套组件适配所有列表样式
ListView 只能实现垂直线性布局,而 RecyclerView 通过LayoutManager机制,轻松支持多种布局:
- 线性布局(垂直 / 水平):对应
LinearLayoutManager,替代 ListView 的核心功能 - 网格布局:对应
GridLayoutManager,实现类似 GridView 的效果,还能动态调整列数 - 瀑布流布局:对应
StaggeredGridLayoutManager,适用于商品展示、图片流等场景 - 自定义布局:通过继承
RecyclerView.LayoutManager,实现任意复杂的列表排列
2. 性能碾压:三级缓存机制详解
RecyclerView 的性能优势核心在于其精密的三级缓存体系,比 ListView 的单一缓存高效得多:
- Scrap 缓存:用于屏幕上可见或即将重新布局的视图,复用时无需重新绑定数据,实现 “零开销” 复用
- Cache 缓存:视图滑出屏幕时暂存于此,用户反向滚动时可快速取回,无需重新绑定数据
- RecycledViewPool:共享回收池,淘汰的视图会进入这里,可被多个 RecyclerView 实例复用,大幅减少视图创建开销
举个直观的例子:当你滑动一个 100 条数据的列表时,ListView 可能需要反复创建和销毁视图,而 RecyclerView 最多只创建 “屏幕可见数量 + 2” 个视图,后续完全通过复用实现,内存占用和渲染效率天差地别。
3. 功能扩展性:自带动画与交互支持
ListView 需要自定义实现的 item 增删动画、拖拽排序等功能,RecyclerView 通过ItemAnimator和ItemTouchHelper提供了原生支持:
- 内置默认动画:item 添加、删除、更新时的过渡动画
- 自定义动画:通过重写
ItemAnimator实现个性化效果 - 拖拽排序 / 侧滑删除:借助
ItemTouchHelper几行代码即可实现
4. 架构解耦:职责分明更易维护
RecyclerView 将数据展示、布局管理、交互逻辑拆分为独立模块:
- Adapter:负责数据与视图的绑定
- LayoutManager:负责视图的排列布局
- ViewHolder:负责缓存 item 视图
- ItemAnimator:负责 item 动画效果这种解耦设计让代码结构更清晰,后续维护和扩展更方便。
二、RecyclerView 基础入门:3 步实现一个标准列表

基础用法看似简单,但很多新手会在依赖配置、组件绑定等细节上踩坑。下面以 “展示用户列表” 为例,带你一步步实现一个可直接运行的 RecyclerView。
1. 第一步:配置依赖与布局文件
(1)引入 RecyclerView 依赖
RecyclerView 属于 AndroidX 库,需在 Module 级别的build.gradle中添加依赖(推荐使用最新稳定版):
dependencies {
// RecyclerView核心依赖
implementation 'androidx.recyclerview:recyclerview:1.3.2'
// 可选:如果需要使用选择功能(如多选、单选)
implementation 'androidx.recyclerview:recyclerview-selection:1.2.0'
}
同步项目后,即可在布局中使用 RecyclerView 组件。注意:避免使用旧版 support 库的 RecyclerView,AndroidX 已成为官方推荐标准。
(2)创建主布局(activity_main.xml)
在 Activity 布局中添加 RecyclerView,注意宽高设置为match_parent(避免显示异常):
<?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:orientation="vertical">
<!-- RecyclerView列表组件 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_user_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp" />
</LinearLayout>
(3)创建 Item 布局(item_user.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_marginBottom="12dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
app:contentPadding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<!-- 用户头像 -->
<ImageView
android:id="@+id/iv_avatar"
android:layout_width="60dp"
android:layout_height="60dp"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher" />
<!-- 文本信息容器 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="14sp"
android:textColor="@android:color/darker_gray" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
这里使用 CardView 包裹 item,让列表更具立体感,实际开发中可根据需求调整。
2. 第二步:创建数据模型与 ViewHolder
(1)数据模型(UserBean.java)
用于存储列表项的数据,根据 item 布局中的展示字段定义:
public class UserBean {
// 用户名
private String name;
// 简介
private String desc;
// 头像资源ID(实际开发中可能是网络图片URL)
private int avatarResId;
// 构造方法
public UserBean(String name, String desc, int avatarResId) {
this.name = name;
this.desc = desc;
this.avatarResId = avatarResId;
}
// Getter和Setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public int getAvatarResId() {
return avatarResId;
}
public void setAvatarResId(int avatarResId) {
this.avatarResId = avatarResId;
}
}
(2)创建 ViewHolder
ViewHolder 的核心作用是缓存 item 中的视图,避免反复调用findViewById(这是 RecyclerView 性能优化的关键之一):
public class UserViewHolder extends RecyclerView.ViewHolder {
// 缓存item中的视图
ImageView ivAvatar;
TextView tvName;
TextView tvDesc;
// 构造方法:初始化视图
public UserViewHolder(@NonNull View itemView) {
super(itemView);
// 绑定视图ID
ivAvatar = itemView.findViewById(R.id.iv_avatar);
tvName = itemView.findViewById(R.id.tv_name);
tvDesc = itemView.findViewById(R.id.tv_desc);
}
// 绑定数据到视图
public void bindData(UserBean userBean) {
// 设置头像(实际开发中推荐用Glide/Picasso加载网络图片)
ivAvatar.setImageResource(userBean.getAvatarResId());
// 设置用户名
tvName.setText(userBean.getName());
// 设置简介
tvDesc.setText(userBean.getDesc());
}
}
将数据绑定逻辑封装在bindData方法中,让 Adapter 代码更简洁。
3. 第三步:实现 Adapter 与 Activity 绑定
Adapter 是 RecyclerView 的 “桥梁”,负责将数据传递给 ViewHolder,并创建 ViewHolder 实例。
(1)实现 Adapter(UserAdapter.java)
public class UserAdapter extends RecyclerView.Adapter<UserViewHolder> {
// 数据集
private List<UserBean> mUserList;
// 构造方法:接收数据集
public UserAdapter(List<UserBean> userList) {
this.mUserList = userList;
}
// 第一步:创建ViewHolder(加载item布局)
@NonNull
@Override
public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 加载item布局文件
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_user, parent, false);
// 返回ViewHolder实例
return new UserViewHolder(itemView);
}
// 第二步:绑定数据到ViewHolder
@Override
public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
// 获取当前位置的数据
UserBean userBean = mUserList.get(position);
// 调用ViewHolder的bindData方法绑定数据
holder.bindData(userBean);
}
// 第三步:返回数据集大小
@Override
public int getItemCount() {
// 避免空指针:数据集为空时返回0
return mUserList == null ? 0 : mUserList.size();
}
// 可选:添加数据更新方法(后续刷新列表用)
public void updateData(List<UserBean> newUserList) {
this.mUserList = newUserList;
// 通知RecyclerView数据已变更
notifyDataSetChanged();
}
}
Adapter 的核心是三个重写方法:onCreateViewHolder(创建视图)、onBindViewHolder(绑定数据)、getItemCount(返回数据量)。
(2)在 Activity 中初始化 RecyclerView
public class MainActivity extends AppCompatActivity {
private RecyclerView mRvUserList;
private UserAdapter mUserAdapter;
private List<UserBean> mUserList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. 初始化RecyclerView
mRvUserList = findViewById(R.id.rv_user_list);
// 2. 设置布局管理器(这里用线性布局,垂直方向)
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRvUserList.setLayoutManager(layoutManager);
// 3. 初始化数据集
initData();
// 4. 初始化Adapter并设置给RecyclerView
mUserAdapter = new UserAdapter(mUserList);
mRvUserList.setAdapter(mUserAdapter);
}
// 初始化测试数据
private void initData() {
mUserList = new ArrayList<>();
// 添加10条测试数据
for (int i = 0; i < 10; i++) {
mUserList.add(new UserBean(
"用户" + (i + 1),
"RecyclerView入门实战,第" + (i + 1) + "条数据",
R.mipmap.ic_launcher
));
}
}
}
到这里,一个标准的 RecyclerView 列表就实现了!运行项目,你会看到一个带卡片效果的垂直列表,滑动流畅且性能优异。
三、RecyclerView 进阶技巧:覆盖 80% 开发场景

基础用法只能满足简单列表需求,实际开发中会遇到各种复杂场景(如多类型布局、下拉刷新、侧滑删除等)。下面这些进阶技巧,让你应对大部分开发需求。
1. 多类型布局:一个列表展示不同样式 Item
很多场景需要在同一个列表中展示不同样式的 item,比如聊天列表(文字消息、图片消息、红包消息)、电商订单列表(待付款、待发货、已完成)。RecyclerView 通过getItemViewType方法轻松实现。
实战场景:聊天列表(文字消息 + 图片消息)
(1)定义消息类型常量与数据模型
// 消息类型常量
public static final int MESSAGE_TYPE_TEXT = 0; // 文字消息
public static final int MESSAGE_TYPE_IMAGE = 1; // 图片消息
// 消息数据模型
public class MessageBean {
private int type; // 消息类型
private String text; // 文字消息内容
private String imageUrl; // 图片消息URL
private boolean isSelf; // 是否是自己发送的消息
// 构造方法、Getter、Setter省略...
}
(2)创建两种 ViewHolder
// 文字消息ViewHolder
public class TextMessageViewHolder extends RecyclerView.ViewHolder {
TextView tvText;
public TextMessageViewHolder(@NonNull View itemView) {
super(itemView);
tvText = itemView.findViewById(R.id.tv_text_message);
}
public void bindData(MessageBean messageBean) {
tvText.setText(messageBean.getText());
// 根据是否是自己发送的消息,调整文字对齐方式
if (messageBean.isSelf()) {
tvText.setGravity(Gravity.END);
tvText.setBackgroundResource(R.drawable.bg_self_message);
} else {
tvText.setGravity(Gravity.START);
tvText.setBackgroundResource(R.drawable.bg_other_message);
}
}
}
// 图片消息ViewHolder
public class ImageMessageViewHolder extends RecyclerView.ViewHolder {
ImageView ivImage;
public ImageMessageViewHolder(@NonNull View itemView) {
super(itemView);
ivImage = itemView.findViewById(R.id.iv_image_message);
}
public void bindData(MessageBean messageBean) {
// 用Glide加载图片(需引入Glide依赖)
Glide.with(itemView.getContext())
.load(messageBean.getImageUrl())
.placeholder(R.mipmap.ic_image_placeholder)
.into(ivImage);
// 调整图片对齐方式
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) ivImage.getLayoutParams();
if (messageBean.isSelf()) {
params.gravity = Gravity.END;
} else {
params.gravity = Gravity.START;
}
ivImage.setLayoutParams(params);
}
}
(3)实现多类型 Adapter
public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<MessageBean> mMessageList;
public MessageAdapter(List<MessageBean> messageList) {
this.mMessageList = messageList;
}
// 关键:返回当前item的类型
@Override
public int getItemViewType(int position) {
return mMessageList.get(position).getType();
}
// 根据item类型创建对应的ViewHolder
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == MESSAGE_TYPE_TEXT) {
// 加载文字消息布局
View itemView = inflater.inflate(R.layout.item_text_message, parent, false);
return new TextMessageViewHolder(itemView);
} else {
// 加载图片消息布局
View itemView = inflater.inflate(R.layout.item_image_message, parent, false);
return new ImageMessageViewHolder(itemView);
}
}
// 根据ViewHolder类型绑定对应数据
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
MessageBean messageBean = mMessageList.get(position);
if (holder instanceof TextMessageViewHolder) {
((TextMessageViewHolder) holder).bindData(messageBean);
} else if (holder instanceof ImageMessageViewHolder) {
((ImageMessageViewHolder) holder).bindData(messageBean);
}
}
@Override
public int getItemCount() {
return mMessageList == null ? 0 : mMessageList.size();
}
}
核心逻辑:通过getItemViewType返回 item 类型,在onCreateViewHolder中根据类型加载不同布局,onBindViewHolder中根据 ViewHolder 类型绑定数据。
2. 下拉刷新与上拉加载:实现无限滚动列表
下拉刷新(PullToRefresh)和上拉加载更多(LoadMore)是列表的常用功能,RecyclerView 结合 SwipeRefreshLayout 和滚动监听即可实现。
(1)布局文件中添加 SwipeRefreshLayout
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_news_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
(2)在 Activity 中实现下拉刷新逻辑
public class NewsActivity extends AppCompatActivity {
private SwipeRefreshLayout mSwipeRefreshLayout;
private RecyclerView mRvNewsList;
private NewsAdapter mNewsAdapter;
private List<NewsBean> mNewsList = new ArrayList<>();
private int mPage = 1; // 当前页码
private static final int PAGE_SIZE = 10; // 每页数据量
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_news);
// 初始化控件
mSwipeRefreshLayout = findViewById(R.id.swipe_refresh_layout);
mRvNewsList = findViewById(R.id.rv_news_list);
// 设置RecyclerView布局管理器
mRvNewsList.setLayoutManager(new LinearLayoutManager(this));
// 初始化Adapter
mNewsAdapter = new NewsAdapter(mNewsList);
mRvNewsList.setAdapter(mNewsAdapter);
// 设置下拉刷新样式
mSwipeRefreshLayout.setColorSchemeResources(
android.R.color.holo_blue_light,
android.R.color.holo_green_light,
android.R.color.holo_orange_light
);
// 下拉刷新监听
mSwipeRefreshLayout.setOnRefreshListener(() -> {
// 下拉刷新时重置页码
mPage = 1;
// 模拟网络请求加载数据
loadNewsData(true);
});
// 上拉加载更多:监听RecyclerView滚动事件
mRvNewsList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// 获取布局管理器
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (layoutManager == null) return;
// 已加载的item总数
int totalItemCount = layoutManager.getItemCount();
// 最后一个可见item的位置
int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
// 条件:滑动到底部 + 不是正在加载中 + 还有更多数据
if (lastVisibleItemPosition >= totalItemCount - 1
&& !mIsLoading
&& mHasMoreData) {
// 加载下一页数据
loadNewsData(false);
}
}
});
// 首次加载数据
loadNewsData(true);
}
// 是否正在加载中
private boolean mIsLoading = false;
// 是否还有更多数据
private boolean mHasMoreData = true;
// 模拟网络请求加载新闻数据
private void loadNewsData(boolean isRefresh) {
mIsLoading = true;
// 如果是下拉刷新,显示刷新动画
if (isRefresh) {
mSwipeRefreshLayout.setRefreshing(true);
} else {
// 上拉加载时,在列表底部添加加载提示
mNewsAdapter.addLoadMoreFooter();
}
// 模拟网络请求(实际开发中替换为Retrofit/OkHttp请求)
new Handler(Looper.getMainLooper()).postDelayed(() -> {
List<NewsBean> newData = new ArrayList<>();
// 模拟生成10条测试数据
for (int i = 0; i < PAGE_SIZE; i++) {
int index = (mPage - 1) * PAGE_SIZE + i;
newData.add(new NewsBean(
"新闻标题" + index,
"新闻摘要:RecyclerView下拉刷新与上拉加载实战案例...",
"2025-11-13",
"https://example.com/image" + index + ".jpg"
));
}
// 处理数据
if (isRefresh) {
// 下拉刷新:清空原有数据,添加新数据
mNewsList.clear();
mNewsList.addAll(newData);
// 重置"是否有更多数据"状态
mHasMoreData = true;
} else {
// 上拉加载:添加新数据
mNewsList.addAll(newData);
// 模拟加载5页后没有更多数据
if (mPage >= 5) {
mHasMoreData = false;
}
}
// 更新Adapter数据
mNewsAdapter.notifyDataSetChanged();
// 加载完成后更新状态
mIsLoading = false;
if (isRefresh) {
mSwipeRefreshLayout.setRefreshing(false);
} else {
// 移除加载提示
mNewsAdapter.removeLoadMoreFooter();
// 如果没有更多数据,添加"没有更多"提示
if (!mHasMoreData) {
mNewsAdapter.addNoMoreFooter();
}
}
// 页码+1
mPage++;
}, 1500); // 模拟网络延迟1.5秒
}
}
(3)Adapter 中添加 Footer 处理
public class NewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int ITEM_TYPE_CONTENT = 0; // 内容item
private static final int ITEM_TYPE_LOAD_MORE = 1; // 加载中Footer
private static final int ITEM_TYPE_NO_MORE = 2; // 没有更多Footer
private List<NewsBean> mNewsList;
private boolean mShowLoadMoreFooter = false;
private boolean mShowNoMoreFooter = false;
// 构造方法省略...
// 重写getItemViewType,添加Footer类型判断
@Override
public int getItemViewType(int position) {
if (mShowNoMoreFooter && position == getItemCount() - 1) {
return ITEM_TYPE_NO_MORE;
}
if (mShowLoadMoreFooter && position == getItemCount() - 1) {
return ITEM_TYPE_LOAD_MORE;
}
return ITEM_TYPE_CONTENT;
}
// 重写getItemCount,添加Footer数量
@Override
public int getItemCount() {
int baseCount = mNewsList == null ? 0 : mNewsList.size();
if (mShowLoadMoreFooter || mShowNoMoreFooter) {
baseCount += 1; // 增加一个Footer
}
return baseCount;
}
// 根据类型创建ViewHolder
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == ITEM_TYPE_CONTENT) {
View itemView = inflater.inflate(R.layout.item_news, parent, false);
return new NewsViewHolder(itemView);
} else if (viewType == ITEM_TYPE_LOAD_MORE) {
View itemView = inflater.inflate(R.layout.item_load_more, parent, false);
return new LoadMoreViewHolder(itemView);
} else {
View itemView = inflater.inflate(R.layout.item_no_more, parent, false);
return new NoMoreViewHolder(itemView);
}
}
// 绑定数据(Footer不需要绑定数据)
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof NewsViewHolder) {
// 注意:Footer会占用position,内容item的position需要调整
int contentPosition = position;
NewsBean newsBean = mNewsList.get(contentPosition);
((NewsViewHolder) holder).bindData(newsBean);
}
}
// 添加加载中Footer
public void addLoadMoreFooter() {
mShowLoadMoreFooter = true;
mShowNoMoreFooter = false;
notifyItemInserted(getItemCount() - 1);
}
// 移除加载中Footer
public void removeLoadMoreFooter() {
if (mShowLoadMoreFooter) {
mShowLoadMoreFooter = false;
notifyItemRemoved(getItemCount());
}
}
// 添加没有更多Footer
public void addNoMoreFooter() {
mShowNoMoreFooter = true;
mShowLoadMoreFooter = false;
notifyItemInserted(getItemCount() - 1);
}
// 内容ViewHolder(新闻item)
public static class NewsViewHolder extends RecyclerView.ViewHolder {
// 实现省略,类似之前的UserViewHolder
}
// 加载中Footer的ViewHolder
public static class LoadMoreViewHolder extends RecyclerView.ViewHolder {
public LoadMoreViewHolder(@NonNull View itemView) {
super(itemView);
}
}
// 没有更多Footer的ViewHolder
public static class NoMoreViewHolder extends RecyclerView.ViewHolder {
public NoMoreViewHolder(@NonNull View itemView) {
super(itemView);
}
}
}
注意事项:上拉加载时要处理 “正在加载中” 状态,避免重复请求;Footer 会占用 item 位置,绑定数据时要注意 position 校准。
3. Item 动画:让列表交互更流畅
RecyclerView 内置了默认的 item 动画,但也支持自定义动画效果,让列表增删改查的交互更生动。
(1)使用默认动画
只需在初始化 RecyclerView 时设置ItemAnimator:
// 设置默认动画(API 21+支持)
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
// 可选:设置动画持续时间
((DefaultItemAnimator) mRecyclerView.getItemAnimator()).setAddDuration(300);
((DefaultItemAnimator) mRecyclerView.getItemAnimator()).setRemoveDuration(300);
默认动画支持 item 添加、删除、更新时的过渡效果,满足基础需求。
(2)自定义 Item 动画(示例:淡入淡出效果)
public class FadeInAnimator extends SimpleItemAnimator {
private ArrayList<RecyclerView.ViewHolder> mPendingRemovals = new ArrayList<>();
private ArrayList<RecyclerView.ViewHolder> mPendingAdditions = new ArrayList<>();
// 处理item添加动画
@Override
public boolean animateAdd(RecyclerView.ViewHolder holder) {
mPendingAdditions.add(holder);
// 初始状态:透明、缩放0.8
holder.itemView.setAlpha(0f);
holder.itemView.setScaleX(0.8f);
holder.itemView.setScaleY(0.8f);
// 执行动画:300毫秒内恢复正常状态
holder.itemView.animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.setDuration(300)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// 动画结束后通知
dispatchAddFinished(holder);
mPendingAdditions.remove(holder);
}
})
.start();
return true;
}
// 处理item删除动画
@Override
public boolean animateRemove(RecyclerView.ViewHolder holder) {
mPendingRemovals.add(holder);
// 执行动画:300毫秒内透明消失
holder.itemView.animate()
.alpha(0f)
.setDuration(300)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
dispatchRemoveFinished(holder);
mPendingRemovals.remove(holder);
}
})
.start();
return true;
}
// 其他抽象方法实现(省略,可参考官方文档)
@Override
public void runPendingAnimations() {}
@Override
public void endAnimation(RecyclerView.ViewHolder holder) {}
@Override
public void endAnimations() {}
@Override
public boolean isRunning() {
return !mPendingRemovals.isEmpty() || !mPendingAdditions.isEmpty();
}
}
使用自定义动画:
mRecyclerView.setItemAnimator(new FadeInAnimator());
4. 点击事件与长按事件:优雅实现交互
RecyclerView 没有像 ListView 那样直接提供setOnItemClickListener方法,需要我们自己通过接口回调实现。
(1)在 Adapter 中定义点击事件接口
public class UserAdapter extends RecyclerView.Adapter<UserViewHolder> {
// 定义点击事件接口
public interface OnItemClickListener {
void onItemClick(int position, UserBean userBean);
}
// 定义长按事件接口
public interface OnItemLongClickListener {
boolean onItemLongClick(int position, UserBean userBean);
}
// 声明接口变量
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
// 设置点击事件监听
public void setOnItemClickListener(OnItemClickListener listener) {
this.mOnItemClickListener = listener;
}
// 设置长按事件监听
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
this.mOnItemLongClickListener = listener;
}
// 在onBindViewHolder中设置点击事件
@Override
public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
UserBean userBean = mUserList.get(position);
holder.bindData(userBean);
// 点击事件
holder.itemView.setOnClickListener(v -> {
if (mOnItemClickListener != null) {
mOnItemClickListener.onItemClick(position, userBean);
}
});
// 长按事件
holder.itemView.setOnLongClickListener(v -> {
if (mOnItemLongClickListener != null) {
return mOnItemLongClickListener.onItemLongClick(position, userBean);
}
return false;
});
}
// 其他方法省略...
}
(2)在 Activity 中设置监听
mUserAdapter.setOnItemClickListener((position, userBean) -> {
// 处理点击事件,比如跳转到用户详情页
Toast.makeText(MainActivity.this, "点击了" + userBean.getName(), Toast.LENGTH_SHORT).show();
Intent intent = new Intent(MainActivity.this, UserDetailActivity.class);
intent.putExtra("USER_DATA", userBean);
startActivity(intent);
});
mUserAdapter.setOnItemLongClickListener((position, userBean) -> {
// 处理长按事件,比如弹出删除菜单
new AlertDialog.Builder(MainActivity.this)
.setTitle("提示")
.setMessage("是否删除" + userBean.getName() + "?")
.setPositiveButton("删除", (dialog, which) -> {
// 从数据集移除数据
mUserList.remove(position);
// 通知RecyclerView删除对应item
mUserAdapter.notifyItemRemoved(position);
// 注意:删除后需要刷新后续item的position,避免错位
mUserAdapter.notifyItemRangeChanged(position, mUserList.size() - position);
})
.setNegativeButton("取消", null)
.show();
return true; // 消费长按事件,避免触发点击事件
});
注意:删除 item 后,需要调用notifyItemRangeChanged刷新后续 item 的 position,否则会导致数据错位。
四、RecyclerView 性能优化:从流畅到极致
RecyclerView 本身性能优异,但如果使用不当,仍会出现卡顿、掉帧等问题。下面这些优化技巧,能让你的列表滑动更丝滑。

1. 优化数据更新:用 DiffUtil 替代 notifyDataSetChanged
notifyDataSetChanged是 “暴力刷新”,会导致所有可见 item 重新绑定数据,性能损耗大。DiffUtil 能精确计算新旧数据的差异,只更新变化的 item。
(1)实现 DiffUtil.Callback
public class UserDiffCallback extends DiffUtil.Callback {
private List<UserBean> mOldList;
private List<UserBean> mNewList;
public UserDiffCallback(List<UserBean> oldList, List<UserBean> newList) {
this.mOldList = oldList;
this.mNewList = newList;
}
// 旧数据和新数据的大小对比
@Override
public int getOldListSize() {
return mOldList == null ? 0 : mOldList.size();
}
@Override
public int getNewListSize() {
return mNewList == null ? 0 : mNewList.size();
}
// 判断两个item是否是同一个对象(通常用唯一ID判断)
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
UserBean oldUser = mOldList.get(oldItemPosition);
UserBean newUser = mNewList.get(newItemPosition);
// 假设UserBean有唯一ID字段userId
return oldUser.getUserId().equals(newUser.getUserId());
}
// 判断两个item的内容是否相同
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
UserBean oldUser = mOldList.get(oldItemPosition);
UserBean newUser = mNewList.get(newItemPosition);
// 对比所有字段是否相同
return oldUser.getName().equals(newUser.getName())
&& oldUser.getDesc().equals(newUser.getDesc())
&& oldUser.getAvatarResId() == newUser.getAvatarResId();
}
// 可选:如果内容不同,返回差异字段(用于局部刷新)
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
UserBean oldUser = mOldList.get(oldItemPosition);
UserBean newUser = mNewList.get(newItemPosition);
Bundle payload = new Bundle();
if (!oldUser.getName().equals(newUser.getName())) {
payload.putString("NAME", newUser.getName());
}
if (!oldUser.getDesc().equals(newUser.getDesc())) {
payload.putString("DESC", newUser.getDesc());
}
return payload.isEmpty() ? null : payload;
}
}
(2)在 Adapter 中使用 DiffUtil
public class UserAdapter extends RecyclerView.Adapter<UserViewHolder> {
// 旧数据集
private List<UserBean> mOldUserList = new ArrayList<>();
// 用DiffUtil更新数据
public void updateData(List<UserBean> newUserList) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
new UserDiffCallback(mOldUserList, newUserList),
true // 是否检测item内容的移动
);
// 更新数据集
mOldUserList.clear();
mOldUserList.addAll(newUserList);
// 应用差异更新
diffResult.dispatchUpdatesTo(this);
}
// 重写onBindViewHolder,支持局部刷新
@Override
public void onBindViewHolder(@NonNull UserViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
// 没有差异,正常绑定全部数据
super.onBindViewHolder(holder, position, payloads);
} else {
// 有差异,只更新变化的字段
Bundle payload = (Bundle) payloads.get(0);
UserBean userBean = mOldUserList.get(position);
if (payload.containsKey("NAME")) {
holder.tvName.setText(payload.getString("NAME"));
}
if (payload.containsKey("DESC")) {
holder.tvDesc.setText(payload.getString("DESC"));
}
}
}
// 其他方法省略...
}
使用 DiffUtil 后,即使列表有 100 条数据,只变化 1 条,也只会更新这 1 条 item,性能提升明显。
2. 布局优化:减少层级与过度绘制
(1)简化 item 布局
item 布局层级越多,渲染时间越长。建议:
- 使用 ConstraintLayout 替代 LinearLayout 嵌套,减少布局层级
- 避免不必要的 View(如空的 TextView、ImageView)
- 合理使用
visibility="gone"替代移除 View
(2)设置 RecyclerView 的 ItemViewCacheSize
默认情况下,RecyclerView 的缓存数量有限,可通过setItemViewCacheSize增加缓存数量,减少onCreateViewHolder的调用:
mRecyclerView.setItemViewCacheSize(20); // 缓存20个item视图
(3)关闭过度绘制
在开发者选项中开启 “显示过度绘制”,避免 item 布局中存在多层背景叠加(如 item 背景 + CardView 背景 + 布局背景)。
3. 图片加载优化:避免 OOM 与卡顿
列表中图片加载是性能瓶颈之一,需注意:
- 使用 Glide/Picasso 等图片加载库,支持图片压缩、缓存、异步加载
- 图片尺寸与 ImageView 大小匹配,避免大图缩放
- 滑动时暂停图片加载,滑动停止后恢复:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
// 滑动停止,恢复图片加载
Glide.with(MainActivity.this).resumeRequests();
} else {
// 滑动中,暂停图片加载
Glide.with(MainActivity.this).pauseRequests();
}
}
});
4. 避免在 onBindViewHolder 中做耗时操作
onBindViewHolder在主线程执行,耗时操作会导致卡顿:
- 避免在其中创建对象(如 String、List),应提前初始化或复用
- 避免复杂计算,可在子线程预处理数据
- 避免频繁调用
notifyItemChanged,尽量批量更新
五、实战案例:打造电商 App 商品列表(支持布局切换)

结合前面的知识点,实现一个仿京东商品列表,支持 “线性布局” 与 “网格布局” 切换,包含下拉刷新、上拉加载、商品点击跳转等功能。
1. 功能需求
- 两种布局切换:线性布局(单列,显示商品详情)、网格布局(双列,简洁展示)
- 下拉刷新:更新商品列表
- 上拉加载:加载更多商品
- 商品点击:跳转到商品详情页
- 支持商品收藏、加入购物车功能
2. 核心代码实现
(1)布局文件(activity_goods_list.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:orientation="vertical">
<!-- 顶部工具栏:包含标题和布局切换按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="@android:color/white"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="商品列表"
android:textSize="20sp"
android:textStyle="bold" />
<!-- 布局切换按钮 -->
<ImageView
android:id="@+id/iv_layout_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_grid_layout"
android:contentDescription="切换布局" />
</LinearLayout>
<!-- 下拉刷新控件 -->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh_goods"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 商品列表RecyclerView -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_goods_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
(2)两种 Item 布局
- 线性布局 item(item_goods_linear.xml):单列,显示商品图片、名称、价格、销量、好评率
- 网格布局 item(item_goods_grid.xml):双列,只显示商品图片、名称、价格
(3)Adapter 实现(GoodsAdapter.java)
public class GoodsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int LAYOUT_TYPE_LINEAR = 0; // 线性布局
private static final int LAYOUT_TYPE_GRID = 1; // 网格布局
private List<GoodsBean> mGoodsList;
private int mCurrentLayoutType = LAYOUT_TYPE_LINEAR; // 当前布局类型
// 点击事件接口
public interface OnGoodsClickListener {
void onGoodsClick(GoodsBean goodsBean);
void onCollectClick(int position, GoodsBean goodsBean);
void onAddCartClick(int position, GoodsBean goodsBean);
}
private OnGoodsClickListener mOnGoodsClickListener;
public void setOnGoodsClickListener(OnGoodsClickListener listener) {
this.mOnGoodsClickListener = listener;
}
// 构造方法
public GoodsAdapter(List<GoodsBean> goodsList) {
this.mGoodsList = goodsList;
}
// 设置布局类型并刷新
public void setLayoutType(int layoutType) {
mCurrentLayoutType = layoutType;
notifyDataSetChanged(); // 布局切换需刷新全部item
}
// 获取当前布局类型
public int getCurrentLayoutType() {
return mCurrentLayoutType;
}
// 根据布局类型返回item视图类型
@Override
public int getItemViewType(int position) {
return mCurrentLayoutType;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == LAYOUT_TYPE_LINEAR) {
// 加载线性布局item
View itemView = inflater.inflate(R.layout.item_goods_linear, parent, false);
return new LinearGoodsViewHolder(itemView);
} else {
// 加载网格布局item
View itemView = inflater.inflate(R.layout.item_goods_grid, parent, false);
return new GridGoodsViewHolder(itemView);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
GoodsBean goodsBean = mGoodsList.get(position);
if (holder instanceof LinearGoodsViewHolder) {
((LinearGoodsViewHolder) holder).bindData(goodsBean, position);
} else if (holder instanceof GridGoodsViewHolder) {
((GridGoodsViewHolder) holder).bindData(goodsBean, position);
}
}
@Override
public int getItemCount() {
return mGoodsList == null ? 0 : mGoodsList.size();
}
// 更新数据(使用DiffUtil)
public void updateData(List<GoodsBean> newGoodsList) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
new GoodsDiffCallback(mGoodsList, newGoodsList), true);
mGoodsList.clear();
mGoodsList.addAll(newGoodsList);
diffResult.dispatchUpdatesTo(this);
}
// 线性布局ViewHolder
public class LinearGoodsViewHolder extends RecyclerView.ViewHolder {
ImageView ivGoodsImage;
TextView tvGoodsName;
TextView tvPrice;
TextView tvSales;
TextView tvRating;
ImageView ivCollect;
Button btnAddCart;
public LinearGoodsViewHolder(@NonNull View itemView) {
super(itemView);
// 绑定视图(省略)
}
public void bindData(GoodsBean goodsBean, int position) {
// 加载商品图片
Glide.with(itemView.getContext())
.load(goodsBean.getImageUrl())
.placeholder(R.mipmap.ic_goods_placeholder)
.into(ivGoodsImage);
// 设置商品名称、价格、销量、好评率(省略)
// 收藏按钮状态
ivCollect.setImageResource(goodsBean.isCollected()
? R.mipmap.ic_collected : R.mipmap.ic_uncollect);
// 商品点击事件
itemView.setOnClickListener(v -> {
if (mOnGoodsClickListener != null) {
mOnGoodsClickListener.onGoodsClick(goodsBean);
}
});
// 收藏点击事件
ivCollect.setOnClickListener(v -> {
if (mOnGoodsClickListener != null) {
mOnGoodsClickListener.onCollectClick(position, goodsBean);
}
});
// 加入购物车点击事件
btnAddCart.setOnClickListener(v -> {
if (mOnGoodsClickListener != null) {
mOnGoodsClickListener.onAddCartClick(position, goodsBean);
}
});
}
}
// 网格布局ViewHolder(实现类似,省略)
public class GridGoodsViewHolder extends RecyclerView.ViewHolder {
// 实现省略...
}
// 商品DiffCallback(实现类似之前的UserDiffCallback,省略)
private static class GoodsDiffCallback extends DiffUtil.Callback {
// 实现省略...
}
}
(4)Activity 实现(GoodsListActivity.java)
public class GoodsListActivity extends AppCompatActivity {
private SwipeRefreshLayout mSwipeRefreshLayout;
private RecyclerView mRvGoodsList;
private GoodsAdapter mGoodsAdapter;
private ImageView mIvLayoutSwitch;
private List<GoodsBean> mGoodsList = new ArrayList<>();
private int mPage = 1;
private boolean mIsLoading = false;
private boolean mHasMoreData = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_goods_list);
// 初始化控件
initView();
// 初始化数据
loadGoodsData(true);
}
private void initView() {
mSwipeRefreshLayout = findViewById(R.id.swipe_refresh_goods);
mRvGoodsList = findViewById(R.id.rv_goods_list);
mIvLayoutSwitch = findViewById(R.id.iv_layout_switch);
// 初始化RecyclerView(默认线性布局)
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRvGoodsList.setLayoutManager(linearLayoutManager);
// 初始化Adapter
mGoodsAdapter = new GoodsAdapter(mGoodsList);
mRvGoodsList.setAdapter(mGoodsAdapter);
// 布局切换点击事件
mIvLayoutSwitch.setOnClickListener(v -> {
int currentType = mGoodsAdapter.getCurrentLayoutType();
if (currentType == GoodsAdapter.LAYOUT_TYPE_LINEAR) {
// 切换到网格布局(2列)
mGoodsAdapter.setLayoutType(GoodsAdapter.LAYOUT_TYPE_GRID);
mRvGoodsList.setLayoutManager(new GridLayoutManager(this, 2));
mIvLayoutSwitch.setImageResource(R.mipmap.ic_linear_layout);
} else {
// 切换到线性布局
mGoodsAdapter.setLayoutType(GoodsAdapter.LAYOUT_TYPE_LINEAR);
mRvGoodsList.setLayoutManager(new LinearLayoutManager(this));
mIvLayoutSwitch.setImageResource(R.mipmap.ic_grid_layout);
}
});
// 下拉刷新监听
mSwipeRefreshLayout.setOnRefreshListener(() -> {
mPage = 1;
loadGoodsData(true);
});
// 上拉加载监听
mRvGoodsList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearManager = (LinearLayoutManager) layoutManager;
int totalItemCount = linearManager.getItemCount();
int lastVisibleItem = linearManager.findLastVisibleItemPosition();
if (!mIsLoading && mHasMoreData && lastVisibleItem >= totalItemCount - 1) {
loadGoodsData(false);
}
} else if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager gridManager = (GridLayoutManager) layoutManager;
int totalItemCount = gridManager.getItemCount();
int lastVisibleItem = gridManager.findLastVisibleItemPosition();
if (!mIsLoading && mHasMoreData && lastVisibleItem >= totalItemCount - 2) {
loadGoodsData(false);
}
}
}
});
// 商品点击事件
mGoodsAdapter.setOnGoodsClickListener(new GoodsAdapter.OnGoodsClickListener() {
@Override
public void onGoodsClick(GoodsBean goodsBean) {
// 跳转到商品详情页
Intent intent = new Intent(GoodsListActivity.this, GoodsDetailActivity.class);
intent.putExtra("GOODS_DATA", goodsBean);
startActivity(intent);
}
@Override
public void onCollectClick(int position, GoodsBean goodsBean) {
// 切换收藏状态
goodsBean.setCollected(!goodsBean.isCollected());
mGoodsAdapter.notifyItemChanged(position);
// 实际开发中需同步到服务器(省略)
}
@Override
public void onAddCartClick(int position, GoodsBean goodsBean) {
// 加入购物车逻辑(省略)
Toast.makeText(GoodsListActivity.this, "已加入购物车", Toast.LENGTH_SHORT).show();
}
});
}
// 加载商品数据(模拟网络请求)
private void loadGoodsData(boolean isRefresh) {
// 实现类似新闻列表的下拉刷新和上拉加载逻辑(省略)
}
}
3. 效果演示
- 初始显示线性布局,展示商品完整信息
- 点击顶部切换按钮,切换为双列网格布局,简洁展示商品
- 下拉刷新可更新商品列表,上拉加载更多商品
- 点击商品跳转到详情页,点击收藏按钮切换收藏状态,点击加入购物车按钮弹出提示
六、实战案例:订单列表(展开 / 收起 + 嵌套 RecyclerView)
另一常见场景:订单列表,每个订单可展开 / 收起,展开后显示订单中的商品列表(嵌套 RecyclerView)。

1. 核心思路
- 外层 RecyclerView:展示订单信息(订单号、时间、金额、展开 / 收起按钮)
- 内层 RecyclerView:每个订单展开后,展示该订单下的商品列表
- 通过订单数据模型中的
isExpanded字段控制展开 / 收起状态 - 点击展开 / 收起按钮时,更新
isExpanded状态并刷新对应 item
2. 关键代码
(1)订单数据模型(OrderBean.java)
public class OrderBean {
private String orderId; // 订单号
private String orderTime; // 下单时间
private double totalPrice; // 订单总金额
private boolean isExpanded; // 是否展开
private List<OrderGoodsBean> goodsList; // 订单中的商品列表
// Getter、Setter、构造方法省略...
}
(2)外层 Adapter(OrderAdapter.java)
public class OrderAdapter extends RecyclerView.Adapter<OrderAdapter.OrderViewHolder> {
private List<OrderBean> mOrderList;
public OrderAdapter(List<OrderBean> orderList) {
this.mOrderList = orderList;
}
@NonNull
@Override
public OrderViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_order, parent, false);
return new OrderViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull OrderViewHolder holder, int position) {
OrderBean orderBean = mOrderList.get(position);
holder.bindData(orderBean, position);
}
@Override
public int getItemCount() {
return mOrderList == null ? 0 : mOrderList.size();
}
public class OrderViewHolder extends RecyclerView.ViewHolder {
TextView tvOrderId;
TextView tvOrderTime;
TextView tvTotalPrice;
TextView tvExpand;
RecyclerView rvOrderGoods;
OrderGoodsAdapter mGoodsAdapter;
public OrderViewHolder(@NonNull View itemView) {
super(itemView);
// 绑定外层订单视图(省略)
rvOrderGoods = itemView.findViewById(R.id.rv_order_goods);
// 初始化内层RecyclerView(网格布局,2列)
rvOrderGoods.setLayoutManager(new GridLayoutManager(itemView.getContext(), 2));
mGoodsAdapter = new OrderGoodsAdapter(new ArrayList<>());
rvOrderGoods.setAdapter(mGoodsAdapter);
}
public void bindData(OrderBean orderBean, int position) {
// 设置订单号、时间、总金额(省略)
// 控制商品列表显示/隐藏
if (orderBean.isExpanded()) {
rvOrderGoods.setVisibility(View.VISIBLE);
tvExpand.setText("收起");
// 更新内层商品列表数据
mGoodsAdapter.updateData(orderBean.getGoodsList());
} else {
rvOrderGoods.setVisibility(View.GONE);
tvExpand.setText("展开");
}
// 展开/收起点击事件
tvExpand.setOnClickListener(v -> {
// 切换展开状态
orderBean.setExpanded(!orderBean.isExpanded());
// 刷新当前item
notifyItemChanged(position);
});
}
}
}
(3)内层 Adapter(OrderGoodsAdapter.java)
用于展示订单中的商品列表,实现类似商品列表的 ViewHolder 和数据绑定(省略,参考前面的 GoodsAdapter)。
3. 注意事项
- 嵌套 RecyclerView 可能导致滑动冲突,需设置内层 RecyclerView 的嵌套滚动:
// 允许内层RecyclerView嵌套滚动
ViewCompat.setNestedScrollingEnabled(rvOrderGoods, true);
- 展开 / 收起时使用
notifyItemChanged刷新 item,避免全局刷新 - 内层 RecyclerView 的布局管理器需根据需求选择(线性 / 网格)
七、RecyclerView 常见问题与避坑指南

1. 数据错位问题
原因:
- ViewHolder 复用导致未重置视图状态(如收藏按钮状态、选中状态)
- 删除 item 后未刷新后续 item 的 position
解决方案:
- 在
onBindViewHolder中重置所有视图状态(不要依赖默认状态) - 删除 item 后调用
notifyItemRangeChanged刷新后续 item - 使用 DiffUtil 精确更新数据,避免批量刷新
2. 滑动卡顿问题
原因:
- item 布局层级过深
onBindViewHolder中存在耗时操作- 图片加载未优化
- 未使用 DiffUtil,频繁调用
notifyDataSetChanged
解决方案:
- 简化 item 布局,使用 ConstraintLayout 减少层级
- 避免在
onBindViewHolder中创建对象、复杂计算 - 图片加载使用 Glide/Picasso,滑动时暂停加载
- 用 DiffUtil 替代
notifyDataSetChanged
3. 布局显示异常(item 不显示 / 显示不全)
原因:
- RecyclerView 或 item 的宽高设置错误(如设置为
wrap_content但未正确测量) - 未设置 LayoutManager(RecyclerView 必须设置 LayoutManager 才能显示)
- 数据集为空或 Adapter 未设置给 RecyclerView
解决方案:
- 确保 RecyclerView 的宽高为
match_parent,item 布局根据需求设置 - 初始化 RecyclerView 后必须调用
setLayoutManager - 检查数据集是否为空,Adapter 是否正确设置
4. 点击事件失效
原因:
- item 布局中存在可点击控件(如 Button、ImageView),抢占了 item 的点击事件
- 点击事件监听未正确设置(如忘记调用
setOnItemClickListener)
解决方案:
- 给可点击控件设置
android:clickable="false",或在代码中设置setClickable(false) - 确保点击事件接口已正确实现并设置给 Adapter
八、总结与扩展
RecyclerView 的核心优势在于 “灵活” 和 “高效”,通过 LayoutManager、Adapter、ViewHolder 的解耦设计,让它能适配几乎所有列表场景。本文从基础用法到进阶技巧,再到两个实战案例,覆盖了 90% 的开发需求,只要掌握这些内容,就能在实际开发中灵活运用 RecyclerView。
扩展学习方向
- 自定义 LayoutManager:实现特殊的列表布局(如环形列表、瀑布流增强版)
- 结合 Jetpack 组件:使用 Paging 3 实现分页加载,Room 实现本地数据缓存
- 复杂交互:item 拖拽排序、侧滑删除(使用 ItemTouchHelper)
- 性能监控:通过 RecyclerView 的
RecyclerView.ItemAnimator和RecyclerView.OnScrollListener监控滑动性能
如果你在使用 RecyclerView 时遇到了特定问题,或者需要实现某个复杂场景,可以在评论区留言,我会为你提供针对性的解决方案!
1541

被折叠的 条评论
为什么被折叠?



