RecyclerView 原理与优化实战

RecyclerView 原理与优化实战

从源码到实践的深度解析

目录

  1. RecyclerView 架构
  2. 回收池机制
  3. LayoutManager 布局
  4. Adapter 原理
  5. 性能优化
  6. 常见问题解决

1. RecyclerView 架构

四大核心组件


flowchart TB
RecyclerView["RecyclerView"] --> Adapter["Adapter<br/>数据绑定"]
RecyclerView --> LayoutManager["LayoutManager<br/>测量 + 布局 + 滚动"]
RecyclerView --> Recycler["Recycler<br/>缓存 + 回收"]
Adapter --> ViewHolder["ViewHolder<br/>itemView + bind"]
LayoutManager --> ViewHolder
Recycler --> ViewHolder

创建 RecyclerView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// XML 布局
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />

// 代码配置
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)

// 设置 LayoutManager (必选)
recyclerView.layoutManager = LinearLayoutManager(this)

// 设置 Adapter (必选)
recyclerView.adapter = MyAdapter(items)

// 设置 ItemDecoration (可选)
recyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))

// 设置 ItemAnimator (可选)
recyclerView.itemAnimator = DefaultItemAnimator()

2. 回收池机制

RecyclerView 缓存层级


flowchart TB
Attached["第 1 层: mAttachedScrap<br/>屏幕内可见的 ViewHolder<br/>用于 data binding 和 position 复用"]
Cache["第 2 层: mCacheViews<br/>默认 2 个<br/>快速复用,不重新 bind"]
Pool["第 3 层: RecyclerViewPool<br/>多个 RecyclerView 共享<br/>按 ViewType 分类"]
Create["第 4 层: Adapter 创建新的 ViewHolder"]
Attached --> Cache --> Pool --> Create

Recycler 核心方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 获取 ViewHolder
fun getViewHolderForPosition(position: Int): ViewHolder {
// 1. 从 AttachedScrap 获取
val scrapHolder = getScrapViewForPosition(position, TYPE_INVALID, true)
if (scrapHolder != null) return scrapHolder

// 2. 从 CacheViews 获取
val cachedHolder = getCachedViewForPosition(position)
if (cachedHolder != null) return cachedHolder

// 3. 从 Pool 获取
val poolHolder = getRecycledViewPool().getRecycledView(viewType)
if (poolHolder != null) return poolHolder

// 4. 创建新的
return createViewHolder(viewType)
}

// 回收 ViewHolder
fun recycleViewHolder(holder: ViewHolder) {
// 检查是否需要保留在 Cache
if (forceRecycle || holder.isRecyclable()) {
// 加入缓存池
recycledViewPool.putRecycledView(holder)
}
}

3. LayoutManager 布局

LinearLayoutManager 原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// onLayoutChildren 实现框架
override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) {
// 1. 分离所有子 View
detachAndScrapAttachedViews(recycler)

// 2. 填充子 View
fill(recycler, state, layoutState)

// 3. 回收不可见的 View
recycleByLayoutState(recycler, layoutState)
}

// fill 方法
private int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state) {
// 从可滚动方向开始填充
while ((layoutState.mLayoutDirection == LayoutState.LAYOUT_START
&& layoutState.hasMore(state))
|| layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {

// 获取 ViewHolder
View view = recycler.getViewForPosition(layoutState.mCurrentPosition);

// 添加到 RecyclerView
addView(view);

// 测量
measureChildWithMargins(view, 0, 0);

// 布局
layoutDecorated(view, left, top, right, bottom);

// 更新位置
layoutState.mCurrentPosition += layoutState.mItemDirection;
}
}

滚动实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// scrollVerticallyBy
override fun scrollVerticallyBy(dy: Int, recycler: Recycler, state: RecyclerView.State): Int {
// 1. 处理填充
if (dy > 0) {
// 向上滚动,填充底部
fill(recycler, state, layoutState)
}

// 2. 移动子 View
offsetChildrenVertical(-dy)

// 3. 回收不可见 View
recyclerByLayoutState(recycler, state)

return dy
}

4. Adapter 原理

ViewHolder 绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 标准 Adapter
class MyAdapter(private val items: List<Item>) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView: TextView = itemView.findViewById(R.id.text)
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_layout, parent, false)
return ViewHolder(view)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = items[position].text
}

override fun getItemCount() = items.size
}

// DiffUtil 差量更新
class MyAdapter : RecyclerView.Adapter<ViewHolder>() {

private val oldItems = mutableListOf<Item>()
private val newItems = mutableListOf<Item>()

fun submitList(newList: List<Item>) {
val diffCallback = DiffCallback(oldItems, newList)
val diffResult = DiffUtil.calculateDiff(diffCallback)

newItems.clear()
newItems.addAll(newList)

diffResult.dispatchUpdatesTo(this)
}
}

class DiffCallback(
private val oldList: List<Item>,
private val newList: List<Item>
) : DiffUtil.Callback() {

override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size

override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {
return oldList[oldPos].id == newList[newPos].id
}

override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {
return oldList[oldPos] == newList[newPos]
}
}

5. 性能优化

RecyclerView 优化清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 设置固定大小
recyclerView.setHasFixedSize(true)

// 2. 使用 Stable IDs (StableId)
// Adapter
class MyAdapter : RecyclerView.Adapter<ViewHolder>() {
override fun getItemId(position: Int): Long = items[position].id
}

// RecyclerView
recyclerView.setItemId(getItemId = true)

// 3. 预取
val layoutManager = LinearLayoutManager(this).apply {
initialPrefetchItemCount = 4
}

// 4. 合并子项
recyclerView.setItemViewCacheSize(20)

// 5. 关闭默认 item 动画 (性能敏感场景)
recyclerView.itemAnimator = null

ViewHolder 优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ 错误:每次 bind 都查找 View
class BadViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(item: Item) {
itemView.findViewById<TextView>(R.id.text).text = item.text // 每次 find
}
}

// ✅ 正确:缓存 View
class GoodViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val textView: TextView = itemView.findViewById(R.id.text) // 缓存

fun bind(item: Item) {
textView.text = item.text
}
}

数据层优化

1
2
3
4
5
6
7
8
9
10
11
12
// ❌ 错误:每次刷新整个列表
adapter.notifyDataSetChanged()

// ✅ 正确:使用 DiffUtil
adapter.submitList(newList)

// ✅ 正确:局部刷新
notifyItemChanged(position) // 单项变化
notifyItemRangeChanged(position, count) // 范围变化
notifyItemRemoved(position) // 删除
notifyItemInserted(position) // 插入
notifyItemMoved(from, to) // 移动

6. 常见问题解决

解决嵌套 RecyclerView 滚动冲突

1
2
3
4
5
6
7
// 方法1: 设置 nested scrolling
innerRecyclerView.isNestedScrollingEnabled = false

// 方法2: 使用自定义 Behavior
recyclerView.layoutManager = object : LinearLayoutManager(context) {
override fun canScrollVertically() = false
}

ItemDecoration 分割线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 简单分割线
class SimpleDividerDecoration : ItemDecoration() {

private val divider: Drawable? = null

override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight

for (i in 0 until parent.childCount - 1) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + divider?.intrinsicHeight ?: 0

divider?.setBounds(left, top, right, bottom)
divider?.draw(c)
}
}
}

面试常问

问题 答案
RecyclerView 缓存层级? Attached → Cache → Pool → create
notifyDataSetChanged vs DiffUtil? DiffUtil 高效,局部更新
为什么滑动卡顿? 绑定太重、View 过多、GC

总结


flowchart TB
Core["RecyclerView 核心"] --> R1["Recycler: 缓存机制,决定复用效率"]
Core --> R2["Adapter: 数据绑定,DiffUtil 差量更新"]
Core --> R3["LayoutManager: 布局 + 滚动"]
Core --> R4["优化: 预取、固定大小、DiffUtil"]


相关文章