Android-高级 UI-08- RecyclerView 源码解析

287 阅读7分钟

RecyclerView 是 Android 提供的一个高效且可扩展的视图组件,专门用于显示大数据集合的列表或网格。它比 ListViewGridView 更加灵活和性能优越,得益于其强大的复用机制和解耦设计。

以下是 RecyclerView 源码的核心模块及其解析。


1 核心模块

  1. Adapter

    • 提供数据集,并负责创建和绑定视图。

    • 核心方法:

      • onCreateViewHolder(ViewGroup parent, int viewType):创建 ViewHolder。
      • onBindViewHolder(VH holder, int position):绑定数据到 ViewHolder。
      • getItemCount():返回数据集大小。
  2. ViewHolder

    • 持有每个列表项的视图,减少多次调用 findViewById() 的开销。
    • 缓存视图引用,提高性能。
  3. LayoutManager

    • 控制 RecyclerView 子项的布局。
    • 决定每个子项的大小和位置。
    • 负责滚动行为。
    • 例如:LinearLayoutManagerGridLayoutManagerStaggeredGridLayoutManager
  4. Recycler

    • 负责管理缓存池中的 ViewHolder
    • 实现视图复用,减少创建新视图的开销。
  5. ItemAnimator

    • 提供列表项增删改的动画效果。
  6. 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 布局流程

  1. 测量阶段

    • onLayoutChildren() 中,通过遍历数据集合和视图池,将需要显示的视图添加到屏幕。
  2. 滚动阶段

    • 滚动通过 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 (回收机制)

缓存

image.png Recycler 管理 ViewHolder 的复用和缓存。它包含以下缓存策略:

  • Attached Scrap:屏幕上不可见但仍附着的视图。
  • Cached Views:短期缓存,容量默认为 2。
  • ViewCacheExtension:用户自定义缓存策略。
  • RecycledViewPool:跨 RecyclerView 的共享池。
  1. mChangeScrap与 mAttachedScrap用来缓存还在屏幕内的 ViewHolder
  2. mCachedViews用来缓存移除屏幕之外的 ViewHolder
  3. mViewCacheExtension这个的创建和缓存完全由开发者自己控制,系统未往这里添加数据
  4. 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()
  • 回收视图加入缓存池,以供复用。

复用

  • 布局时候复用

image.png

  1. getChangedScrapViewForPosition --》 mChangedScrap (position、StableId)
  2. getScrapOrHiddenOrCachedHolderForPosition --》 mAttachedScrap、mCachedViews(ForPosition)
  3. getScrapOrCachedViewForId --》 mAttachedScrap、mCachedViews(StableId)
  4. mViewCacheExtension.getViewForPositionAndType --》 自定义复用,缓存需要自己实现
  5. getRecycledViewPool().getRecycledView
  6. mAdapter.createViewHolder --> onCreateViewHolder --> 创建 ViewHolder 对象
  7. tryBindViewHolderByDeadline --> onBindViewHolder --> 处理数据

2.4. Draw(绘制流程)

绘制流程包含以下步骤:

  1. RecyclerView.dispatchDraw()

    • 遍历所有子视图,调用 drawChild()
    • 绘制 ItemDecorationItemAnimator
  2. 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 优化点

  1. DiffUtil:高效计算数据集变化,减少不必要的刷新。
  2. Pre-fetching:通过 GapWorker 提前加载数据,提升滚动流畅度。
  3. ViewHolder 缓存:通过 Recycler 缓存复用视图,减少性能开销。
  4. Payload:部分更新数据,避免全量刷新。

4 RecyclerView自定义分割线

RecyclerViewItemDecoration 提供了灵活的方式在列表项之间添加分割线。通过自定义分割线,你可以根据需求实现丰富的效果,比如指定颜色、宽度、间距,甚至不同类型的分割线。


4.1 实现步骤

1. 创建自定义的 ItemDecoration

创建一个继承自 RecyclerView.ItemDecoration 的类,并重写需要的方法。

方法功能
getItemOffsets为每个 Item 设置边距。
onDrawRecyclerViewItem 之前绘制分割线。
onDrawOverRecyclerViewItem 上方绘制分割线。

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 的设计解耦了布局、动画和数据,便于扩展。其高效的复用机制和灵活的架构,适合处理复杂列表和大数据集合。掌握其源码能更好地优化性能,适应各种业务场景。

相关参考: