RecyclerView 是 Android 提供的一个高效且可扩展的视图组件,专门用于显示大数据集合的列表或网格。它比 ListView 和 GridView 更加灵活和性能优越,得益于其强大的复用机制和解耦设计。
以下是 RecyclerView 源码的核心模块及其解析。
1 核心模块
-
Adapter
-
提供数据集,并负责创建和绑定视图。
-
核心方法:
onCreateViewHolder(ViewGroup parent, int viewType):创建 ViewHolder。onBindViewHolder(VH holder, int position):绑定数据到 ViewHolder。getItemCount():返回数据集大小。
-
-
ViewHolder
- 持有每个列表项的视图,减少多次调用
findViewById()的开销。 - 缓存视图引用,提高性能。
- 持有每个列表项的视图,减少多次调用
-
LayoutManager
- 控制
RecyclerView子项的布局。 - 决定每个子项的大小和位置。
- 负责滚动行为。
- 例如:
LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager。
- 控制
-
Recycler
- 负责管理缓存池中的
ViewHolder。 - 实现视图复用,减少创建新视图的开销。
- 负责管理缓存池中的
-
ItemAnimator
- 提供列表项增删改的动画效果。
-
ItemDecoration
- 提供额外的绘制功能(如分割线、边距)。
2 核心源码解析
2.1. Adapter 和 ViewHolder
Adapter 是 RecyclerView 和数据的桥梁,ViewHolder 是缓存视图的容器。
关键方法:
java
复制代码
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.textView.setText(dataList.get(position));
}
@Override
public int getItemCount() {
return dataList.size();
}
ViewHolder 缓存机制:
RecyclerView.Adapter.createViewHolder()用于创建 ViewHolder。RecyclerView.Adapter.bindViewHolder()用于绑定数据到 ViewHolder。- 如果 ViewHolder 已存在,则直接复用。
2.2. LayoutManager
LayoutManager 是 RecyclerView 的核心,负责:
- 子视图的测量和布局。
- 滚动逻辑。
- 回收不需要的视图。
源码解析:LinearLayoutManager 布局流程
-
测量阶段
- 在
onLayoutChildren()中,通过遍历数据集合和视图池,将需要显示的视图添加到屏幕。
- 在
-
滚动阶段
- 滚动通过
scrollBy()或scrollToPosition()完成。 RecyclerView调用LayoutManager.scrollVerticallyBy()更新屏幕上的内容。
- 滚动通过
关键方法:
java
复制代码
@Override
public void onLayoutChildren(Recycler recycler, State state) {
detachAndScrapAttachedViews(recycler);
int offsetY = 0;
for (int i = 0; i < getItemCount(); i++) {
View view = recycler.getViewForPosition(i);
addView(view);
measureChildWithMargins(view, 0, 0);
int width = getDecoratedMeasuredWidth(view);
int height = getDecoratedMeasuredHeight(view);
layoutDecorated(view, 0, offsetY, width, offsetY + height);
offsetY += height;
}
}
2.3. Recycler (回收机制)
缓存
Recycler 管理 ViewHolder 的复用和缓存。它包含以下缓存策略:
- Attached Scrap:屏幕上不可见但仍附着的视图。
- Cached Views:短期缓存,容量默认为 2。
- ViewCacheExtension:用户自定义缓存策略。
- RecycledViewPool:跨
RecyclerView的共享池。
- mChangeScrap与 mAttachedScrap用来缓存还在屏幕内的 ViewHolder
- mCachedViews用来缓存移除屏幕之外的 ViewHolder
- mViewCacheExtension这个的创建和缓存完全由开发者自己控制,系统未往这里添加数据
- RecycledViewPool ViewHolder 缓存池
缓存 --> onLayoutChildren --> detachAndScrapAttachedViews(recycler); --> recycler.scrapView(view);
fill
--》 recycleViewsFromStart
--》 recycleChildren
--》 removeAndRecycleViewAt
--》 recycler.recycleView
--》 recycleViewHolderInternal // 关键代码,(cacheView、pool)
--》 mCachedViews
--》 如果满了 --》 recycleCachedViewAt(0);
--》 新的Item肯定是放在 CacheView 中的
--》 CacheView 把老的移除集合,放入 pool 中
--》 Pool
--》 addViewHolderToRecycledViewPool(viewHolder, true); // 添加到Pool中
--》 getRecycledViewPool().putRecycledView(holder);
--》 viewType = scrap.getItemViewType();
--》 scrap.resetInternal();
--》 scrapHeap.add(scrap);
getScrapDataForType(viewType)
ArrayList<ViewHolder> mCachedViews --》 DEFAULT_CACHE_SIZE = 2 --> setViewCacheSize
ArrayList<ViewHolder> scrapHeap --》 DEFAULT_MAX_SCRAP = 5 --> setMaxRecycledViews
SparseArray<ScrapData> mScrap ---> Map<int,ScrapData>
--> ArrayList<ViewHolder> mScrapHeap
- ViewHolder
从缓存池中 复用 ViewHolder :需要调用 onBindViewHOdler 从 CacheView 复用: 不用调用 onBindViewHOdler 从缓存中没有拿到 ViewHolder: onCreate onBind
回收流程:
- 当视图滚出屏幕时,调用
recycler.recycleView()。 - 回收视图加入缓存池,以供复用。
复用
- 布局时候复用
- getChangedScrapViewForPosition --》 mChangedScrap (position、StableId)
- getScrapOrHiddenOrCachedHolderForPosition --》 mAttachedScrap、mCachedViews(ForPosition)
- getScrapOrCachedViewForId --》 mAttachedScrap、mCachedViews(StableId)
- mViewCacheExtension.getViewForPositionAndType --》 自定义复用,缓存需要自己实现
- getRecycledViewPool().getRecycledView
- mAdapter.createViewHolder --> onCreateViewHolder --> 创建 ViewHolder 对象
- tryBindViewHolderByDeadline --> onBindViewHolder --> 处理数据
2.4. Draw(绘制流程)
绘制流程包含以下步骤:
-
RecyclerView.dispatchDraw():- 遍历所有子视图,调用
drawChild()。 - 绘制
ItemDecoration和ItemAnimator。
- 遍历所有子视图,调用
-
onDraw(Canvas canvas):- 绘制自定义内容。
LienarLayoutManager --> layoutChunk --> measureChildWithMargins --> --> RecyclerView.getItemDecorInsetsForChild
自定义View的绘制流程 --- View.draw ReyclerView.draw --> super.draw(c); --> 绘制自己 --> ReyclerView.onDraw --> ItemDecoration.onDraw --> 绘制孩子(ItemView) --> ItemDecoration.onDrawOver()
ItemDecoration.onDraw --》 绘制孩子(ItemView) --》 ItemDecoration.onDrawOver
后面绘制的,会覆盖前面绘制的
outRect --》 onDraw,onDrawOver 没有区别
2.5. 滚动(滑动机制)
滑动事件由 RecyclerView 内部处理,通过 OnScrollListener 回调。
-
Fling 机制:
- 实现快速滑动的惯性效果。
- 使用
OverScroller控制滚动速度。
源码解析:滚动事件
java
复制代码
@Override
public boolean onTouchEvent(MotionEvent e) {
boolean handled = mGestureDetector.onTouchEvent(e);
if (e.getAction() == MotionEvent.ACTION_UP) {
releaseGlows();
}
return handled || super.onTouchEvent(e);
}
嵌套滑动
--》 RecyclerView
--》 dispatchNestedPreScroll --> scrollByInternal --> dispatchNestedScroll --> 边缘效果
--》 scrollByInternal
--》scrollStep(x, y, mReusableIntPair);
--> mLayout.scrollHorizontallyBy
--> mLayout.scrollVerticallyBy
--> scrollBy
--> fill
--> layoutChunk
--> View view = layoutState.next(recycler);
--》 recycler.getViewForPosition
--》 tryGetViewHolderForPositionByDeadline // 主要的复用代码
--> measureChildWithMargins(view, 0, 0); // 测量,padding margin inset(分割线的空间)
3 RecyclerView 优化点
- DiffUtil:高效计算数据集变化,减少不必要的刷新。
- Pre-fetching:通过
GapWorker提前加载数据,提升滚动流畅度。 - ViewHolder 缓存:通过 Recycler 缓存复用视图,减少性能开销。
- Payload:部分更新数据,避免全量刷新。
4 RecyclerView自定义分割线
RecyclerView 的 ItemDecoration 提供了灵活的方式在列表项之间添加分割线。通过自定义分割线,你可以根据需求实现丰富的效果,比如指定颜色、宽度、间距,甚至不同类型的分割线。
4.1 实现步骤
1. 创建自定义的 ItemDecoration 类
创建一个继承自 RecyclerView.ItemDecoration 的类,并重写需要的方法。
| 方法 | 功能 |
|---|---|
getItemOffsets | 为每个 Item 设置边距。 |
onDraw | 在 RecyclerView 的 Item 之前绘制分割线。 |
onDrawOver | 在 RecyclerView 的 Item 上方绘制分割线。 |
2. 自定义分割线实现示例
以下示例代码实现了一个带有自定义高度和颜色的分割线。
java
复制代码
public class CustomDividerItemDecoration extends RecyclerView.ItemDecoration {
private final int dividerHeight; // 分割线高度
private final Paint paint; // 用于绘制分割线的 Paint
public CustomDividerItemDecoration(int color, int height) {
this.dividerHeight = height;
// 初始化 Paint
paint = new Paint();
paint.setColor(color);
paint.setStyle(Paint.Style.FILL);
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
// 为每个 Item 底部添加分割线的高度
outRect.bottom = dividerHeight;
}
@Override
public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(canvas, parent, state);
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
// 遍历 RecyclerView 的所有子项
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
// 分割线的顶部位置
int top = child.getBottom() + params.bottomMargin;
int bottom = top + dividerHeight;
// 绘制分割线
canvas.drawRect(left, top, right, bottom, paint);
}
}
}
3. 在 RecyclerView 中使用自定义分割线
java
复制代码
RecyclerView recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// 设置自定义分割线
CustomDividerItemDecoration divider = new CustomDividerItemDecoration(
Color.GRAY, // 分割线颜色
10 // 分割线高度(单位:px)
);
recyclerView.addItemDecoration(divider);
// 设置适配器
recyclerView.setAdapter(new MyRecyclerViewAdapter());
4.2 实现其他功能的分割线
1. 仅为部分位置添加分割线
在 getItemOffsets 方法中判断 Item 的位置来决定是否添加分割线。
java
复制代码
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
if (position % 2 == 0) {
outRect.bottom = dividerHeight; // 仅偶数位置添加分割线
}
}
2. 实现不同颜色或形状的分割线
在 onDraw 方法中,根据条件更改分割线颜色或绘制图形。
java
复制代码
@Override
public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(canvas, parent, state);
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin;
int bottom = top + dividerHeight;
// 根据位置改变颜色
if (i % 2 == 0) {
paint.setColor(Color.BLUE);
} else {
paint.setColor(Color.RED);
}
canvas.drawRect(left, top, right, bottom, paint);
}
}
3. 为网格布局添加分割线
为 GridLayoutManager 定制的分割线需要考虑网格的行和列。
java
复制代码
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
int spanCount = 2; // 列数(根据实际情况设置)
int spacing = dividerHeight;
// 设置左右边距
if (position % spanCount == 0) {
outRect.right = spacing / 2;
} else {
outRect.left = spacing / 2;
}
// 设置上下边距
if (position >= spanCount) {
outRect.top = spacing;
}
}
4.3 总结
ItemDecoration是 RecyclerView 提供的用于自定义分割线或间距的工具,具有很高的灵活性。- 可以实现简单的线条、复杂的形状,甚至背景图等各种需求。
- 在实际使用中,要根据布局管理器(线性、网格、瀑布流)调整分割线逻辑。
5 总结
RecyclerView 的设计解耦了布局、动画和数据,便于扩展。其高效的复用机制和灵活的架构,适合处理复杂列表和大数据集合。掌握其源码能更好地优化性能,适应各种业务场景。
相关参考: