RecyclerView — Android Jetpack 组件,用于以列表和网格形式高效显示大型数据集。与 ListView 不同,RecyclerView 重复使用 ViewHolder 并通过 LayoutManager 管理布局。根据 Google(Android Developers, 2026)的数据,RecyclerView 在滚动包含 1000+ 个元素的列表时,凭借 ViewHolder 池和 DiffUtil 差异算法,可处理高达 每秒 60 帧 的渲染。有关 Android 应用程序架构的更多信息,请阅读 我们关于 ListView 的文章。
要点
RecyclerView — Android Jetpack 库的组件,于 Google I/O 2014 上作为 Android Support Library v7 的一部分推出。它用于高性能显示动态列表和网格。RecyclerView 作为 ListView 的替代品出现,解决了其前身的关键问题——缺少强制性的 ViewHolder 重复使用。
RecyclerView 的架构基于 Model-View-Adapter 模式:数据存储在模型中 (List, LiveData, Flow),显示由 Adapter 管理,布局由 LayoutManager 控制。每个组件负责自己的职责领域,使系统灵活且可扩展。
根据 Android Developers (2025) 的调查,RecyclerView 在 Google Play 前 100 名中有 78% 的 Android 应用中使用。凭借三种机制——ViewHolder 池、差异更新计算和异步数据加载——包含 1000+ 个元素的列表滚动性能在中端设备上稳定在 60 FPS。
RecyclerView 适用于:元素数量未知的列表(信息流、聊天、日志)、N 列网格(画廊、目录)、瀑布流布局(Pinterest 看板)、带有添加/删除元素动画的列表。对于包含 2-5 个元素的简单屏幕,LinearLayout 或 ScrollView 更简单高效——RecyclerView 会增加开销。
要使用 RecyclerView,请在 build.gradle (app) 中添加依赖:implementation 'androidx.recyclerview:recyclerview:1.4.0'。最低 API 版本为 14 (Android 4.0)。从 AndroidX RecyclerView 1.3.0 开始,可使用 recyclerview-selection 模块进行元素选择和 recyclerview-swipe 模块进行滑动操作。
RecyclerView 分为三个独立层:Adapter 管理数据和 ViewHolder,LayoutManager 负责元素定位,ItemAnimator 控制动画。这种架构允许在不更改其他层的情况下替换任何层——例如,从 LinearLayoutManager 切换到 GridLayoutManager 而无需更改 Adapter 代码。
| 组件 | 职责 | 实现 |
|---|---|---|
| Adapter | 创建 ViewHolder,将数据绑定到 View,通知变化 | RecyclerView.Adapter<VH> |
| ViewHolder | 缓存 View 引用,避免 findViewById() | RecyclerView.ViewHolder |
| LayoutManager | 定位元素,确定滚动方向 | LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager |
| ItemAnimator | 添加、删除、移动元素的动画 | DefaultItemAnimator |
| ItemDecoration | 绘制元素之间的分隔线、间距、背景 | RecyclerView.ItemDecoration |
工作循环:(1) Adapter 通过 onCreateViewHolder() 创建 ViewHolder,(2) LayoutManager 确定新 View 在屏幕上的位置,(3) Adapter 通过 onBindViewHolder() 绑定数据,(4) 滚动时,离开屏幕的 ViewHolder 进入池 (RecycledViewPool) 以重复使用。不创建新的 View,而是重复使用现有的 View——这是关键的性能机制。
ViewHolder — 在列表元素内部存储 View(TextView、ImageView 等)引用的类。如果没有 ViewHolder,每次滚动系统都会调用 findViewById()——Android UI 中最昂贵的操作之一。ViewHolder 在第一次绑定后记住引用,从而消除此调用。
// 数据模型
data class Article(
title: String,
summary: String,
author: String,
imageUrl: String?
)
// ViewHolder — 存储对 View 的引用
class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val titleText = itemView.findViewById<TextView>(R.id.tvTitle)
private val summaryText = itemView.findViewById<TextView>(R.id.tvSummary)
private val authorText = itemView.findViewById<TextView>(R.id.tvAuthor)
fun bind(article: Article) {
titleText.text = article.title
summaryText.text = article.summary
authorText.text = article.author
}
}
实践规则:项目布局中的每个 View 应使用 findViewById() 在构造函数中声明为 ViewHolder 的属性恰好一次。bind() 方法不调用 findViewById(),仅设置文本、颜色和监听器。根据 Google(Android Performance Patterns, 2025),这种方法可将列表滚动速度提高 30-50%。
LayoutManager 决定元素在 RecyclerView 内部的排列方式。默认情况下,Android 提供三种实现:LinearLayoutManager、GridLayoutManager 和 StaggeredGridLayoutManager。LayoutManager 的选择影响用户对数据的感知和滚动性能。
| LayoutManager | 排列方式 | 使用场景 | 设置方法 |
|---|---|---|---|
| LinearLayoutManager | 列表(垂直或水平) | 信息流、聊天、通知、产品列表 | LinearLayoutManager(context) |
| GridLayoutManager | N 列网格 | 画廊、目录、图标、图片网格 | GridLayoutManager(context, spanCount) |
| StaggeredGridLayoutManager | 瀑布流网格(元素高度不同) | Pinterest 看板、笔记、瀑布流画廊 | StaggeredGridLayoutManager(spanCount, orientation) |
// 在 Fragment 中设置 LinearLayoutManager
class ArticleListFragment : Fragment() {
private lateinit var binding: FragmentArticleListBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val recyclerView = binding.recyclerView
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter = ArticleAdapter(articlesList)
// 优化:固定大小 + 池
recyclerView.setHasFixedSize(true)
recyclerView.setItemViewCacheSize(20)
// 元素之间的分隔线
val divider = DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
recyclerView.addItemDecoration(divider)
}
}
性能:带垂直滚动的 LinearLayoutManager 是最快的选项,因为元素按顺序排列且 LayoutManager 以 O(1) 计算位置。GridLayoutManager 需要额外的计算来分割列,但在固定 spanCount 下保持高效。StaggeredGridLayoutManager 是三者中最慢的,因为它必须考虑相邻元素的高度来计算元素在瀑布流中的位置。仅当视觉上需要不同单元格高度时才使用它。
Adapter 在 RecyclerView 中实现 GoF 的 Adapter 模式:将数据 (List<T>) 转换为屏幕上显示的 ViewHolder。数据变化时,Adapter 可以通过 notifyDataSetChanged()、notifyItemInserted() 等方法通知 RecyclerView。问题是 notifyDataSetChanged() 会重绘所有可见元素,导致卡顿 (jank)。
// 使用 DiffUtil 进行高效更新的 Adapter
class ArticleAdapter : RecyclerView.Adapter<ArticleViewHolder>() {
private var articles: List<Article> = emptyList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_article, parent, false)
return ArticleViewHolder(view)
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
holder.bind(articles[position])
}
override fun getItemCount(): Int = articles.size
// 通过 DiffUtil 更新列表
fun updateArticles(newArticles: List<Article>) {
val diffCallback = ArticleDiffCallback(articles, newArticles)
val diffResult = DiffUtil.calculateDiff(diffCallback)
articles = newArticles
diffResult.dispatchUpdatesTo(this)
}
}
// DiffUtil 的回调
class ArticleDiffCallback(
private val oldList: List<Article>,
private val newList: List<Article>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {
return oldList[oldPos].title == newList[newPos].title
}
override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {
return oldList[oldPos] == newList[newPos]
}
}
DiffUtil 使用 Eugene W. Myers 差异算法计算将旧列表转换为新列表所需的最小操作数(insert、remove、move、change)。对于 100 个元素的列表,DiffUtil 在 1-5 毫秒 内工作(Google, 2025)。对于大型列表 (10000+),请使用 Paging 库中的 AsyncListDiffer 或 PagingDataAdapter——它们在后台线程中执行计算。
ListView — RecyclerView 的前身,出现在 Android 1.0 (API 1) 中。它也显示列表,但不强制 ViewHolder,不支持 LayoutManager,也无法动画显示变化。RecyclerView 被设计为具有更严格架构和更好性能的 ListView 替代品。
| 标准 | RecyclerView | ListView |
|---|---|---|
| ViewHolder | 必需(架构) | 推荐但不必需 |
| LayoutManager | 划分:列表、网格、瀑布流 | 仅垂直列表 |
| 变化动画 | DefaultItemAnimator 开箱即用 | 无内置支持 |
| 变化通知 | 点式:notifyItemInserted/Removed | notifyDataSetChanged() 仅通知整个列表 |
| DiffUtil | 通过 DiffUtil.Callback 支持 | 无等效功能 |
| 分隔线 | ItemDecoration(自定义) | android:divider(内置) |
| 最低版本 | API 14 (Android 4.0) 通过 AndroidX | API 1(所有版本) |
Google 建议(Android Developers, 2026):对所有新项目使用 RecyclerView。ListView 可以保留在迁移成本不合理的旧代码库 (legacy) 中。RecyclerView 在 100+ 个元素上提供更好的性能,无需额外方案即可支持水平滚动和网格。在 Android 15 中,ListView 已被正式标记为部分弃用。
常见问题
检查三个条件:(1) Adapter 已通过 recyclerView.adapter = adapter 设置,(2) LayoutManager 已通过 recyclerView.layoutManager = LinearLayoutManager(context) 设置,(3) 列表中的数据不为空且 adapter 已收到通知 (notifyDataSetChanged())。典型错误——在调用 notifyDataSetChanged() 之后设置数据。数据应在通知之前分配。检查列表大小:adapter.itemCount 应大于 0。
卡顿 (jank) 的产生原因是 onBindViewHolder() 执行了繁重操作:无缓存的图片加载、bind 内部的 findViewById()、复杂计算。解决方案:(1) 使用 Glide 或 Coil 库进行异步图片加载,(2) 将所有 findViewById() 移到 ViewHolder 构造函数中,(3) 差异计算使用 AsyncListDiffer。在 Android Studio 中启用 GPU 分析 (Profile GPU Rendering) 来查找慢帧。
向 LayoutManager 传递方向:LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)。第二个参数是 reverseLayout (false = 从左到右)。对于水平网格,使用带有水平方向的 GridLayoutManager 或自定义 LinearSnapHelper 实现分页滚动。水平列表适合产品轮播、图片、分类。
RecyclerView 没有内置的 onClickListener(与带有 onItemClickListener 的 ListView 不同)。在 ViewHolder 中实现:将 lambda 传递给构造函数并设置 itemView.setOnClickListener。示例:class ArticleViewHolder(itemView: View, val onItemClick: (Article) -> Unit) : RecyclerView.ViewHolder(itemView)。在 bind() 内部调用 onItemClick(article)。替代方案——设置 RecyclerView.addOnItemTouchListener() 处理手势。
RecycledViewPool — 滚动时变为不可见的 ViewHolder 的内部池。它们不会被销毁,而是保存在池中并重复使用。默认情况下,池每种类型存储 5 个 ViewHolder。对于复杂布局,增加大小:recyclerView.getRecycledViewPool().setMaxRecycledViews(VIEW_TYPE_NORMAL, maxCount)。如果 RecyclerView 有多个 View 类型 (viewType),池为每种类型存储单独的堆栈。在不同 RecyclerView 之间共享 RecycledViewPool 可节省包含嵌套列表的屏幕的内存。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。