02_ChoicesWang/RecyclerView_Divider介绍和源码分析

114 阅读6分钟

1. 引言

在Android开发中,RecyclerView是一个非常常用的组件,用于显示大量数据项。为了使数据项之间的视觉分隔更加清晰,我们通常会使用分隔线。ChoicesWang/RecyclerView_Divider是一个开源库,提供了一个自定义的ItemDecoration实现,用于在RecyclerView中绘制分隔线。 本篇文章将介绍ChoicesWang/RecyclerView_Divider库,并深入分析其源码实现。

2. 库的介绍

ChoicesWang/RecyclerView_Divider库提供了一个高度灵活且易于使用的分隔线实现。 用户可以自定义分隔线的颜色、大小和边距,并支持多种布局管理器,包括GridLayoutManagerLinearLayoutManager。通过实现DividerLookup接口,用户可以根据不同的需求动态定义分隔线的样式和位置。

代码示例:

定义分隔线的样式和位置

// GridDividerLookup类实现DividerLookup接口,根据位置返回不同的分隔线
class GridDividerLookup : DividerItemDecoration.DividerLookup {
    override fun getVerticalDivider(position: Int): Divider {
        return when (position % 15) {
            2 -> Divider.Builder()
                .size(4)
                .color(Color.GREEN)
                .build()
            else -> {
                Divider.Builder()
                    .size(1)
                    .color(Color.LTGRAY)
                    .build()
            }
        }
    }

    override fun getHorizontalDivider(position: Int): Divider {
        return when (position % 15) {
            1 ->  Divider.Builder()
                .size(5)
                .color(Color.RED)
                .build()
            else -> {
                Divider.Builder()
                    .size(1)
                    .color(Color.GREEN)
                    .build()
            }
        }
    }
}

添加分割线

recyclerView.layoutManager = GridLayoutManager(context, 3)
itemDecoration.setDividerLookup(GridDividerLookup())
recyclerView.addItemDecoration(itemDecoration)

3. 源码分析

3.1 Divider类

Divider类用于定义分隔线的属性,包括颜色、大小和边距。

public class Divider {
    @ColorInt
    public int color;
    public int size;
    public int marginStart;
    public int marginEnd;
}

这个类使用简单的属性封装了分隔线的定义,可以很容易地在DividerItemDecoration中使用。

3.2 DividerItemDecoration类

DividerItemDecoration类是实现分隔线绘制的核心类,继承自RecyclerView.ItemDecoration

onDraw和DividerLookup

public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    private Paint mPaint;
    private DividerLookup dividerLookup;
    
    public void setDividerLookup(DividerLookup dividerLookup) {
        this.dividerLookup = dividerLookup;
    }

    public interface DividerLookup {
        Divider getVerticalDivider(int position);
        Divider getHorizontalDivider(int position);
    }

    // 重写onDraw方法,用于绘制分隔线
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        int childCount = parent.getChildCount(); // 获取RecyclerView子项的数量
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i); // 获取每个子项
            int position = parent.getChildAdapterPosition(child); // 获取子项的位置
            Divider leftDivider = dividerLookup.getVerticalDivider(position); // 获取竖直方向的分隔线
            Divider bottomDivider = dividerLookup.getHorizontalDivider(position); // 获取水平方向的分隔线
            if (leftDivider != null) drawLeftDivider(c, child, leftDivider); // 如果有竖直分隔线,绘制它
            if (bottomDivider != null) drawBottomDivider(c, child, bottomDivider); // 如果有水平分隔线,绘制它
        }
    }
}
  • DividerLookup接口:定义了获取竖直和水平分隔线的方法,可以根据位置返回不同的分隔线。
  • onDraw方法:遍历RecyclerView的所有子项,并根据位置获取竖直和水平的分隔线,调用相应的方法绘制分隔线。

drawBottomDivider方法和drawLeftDivider方法

drawBottomDivider 方法和 drawLeftDivider 方法,分别用于绘制底部分隔线和左侧分隔线。

public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    // 绘制底部分隔线
    private void drawBottomDivider(Canvas c, View child, Divider bd) {
        mPaint.setColor(bd.color);
        c.drawRect(
                child.getLeft() + bd.marginStart, // 起始边距
                child.getBottom(), // 子项底部
                child.getRight() - bd.marginEnd, // 结束边距
                child.getBottom() + bd.size, // 分隔线的厚度
                mPaint
        );
    }

    // 绘制左侧分隔线
    private void drawLeftDivider(Canvas c, View child, Divider ld) {
        mPaint.setColor(ld.color);
        c.drawRect(
                child.getRight(), // 子项右边
                child.getTop() + ld.marginStart, // 起始边距
                child.getRight() + ld.size, // 分隔线的厚度
                child.getBottom() - ld.marginEnd, // 结束边距
                mPaint
        );
    }
}

getItemOffsets

用于设置每个子项的偏移量,以确保分隔线能够正确显示。根据RecyclerView的LayoutManager类型(GridLayoutManager或LinearLayoutManager)进行不同的处理。

public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    // 重写getItemOffsets方法,用于设置子项的偏移量
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {

        int itemCount = state.getItemCount(); // 获取总的子项数量
        int position = parent.getChildAdapterPosition(view); // 获取当前子项的位置
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();

        // 如果是GridLayoutManager
        if (layoutManager instanceof GridLayoutManager) {
            GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            int spanCount = gridLayoutManager.getSpanCount();
            GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();
            int spanIndex = spanSizeLookup.getSpanIndex(position, spanCount);
            int spanGroupIndex = spanSizeLookup.getSpanGroupIndex(position, spanCount);
            int lastSpanGroupIndex = spanSizeLookup.getSpanGroupIndex(itemCount - 1, spanCount);
            Divider vd = dividerLookup.getVerticalDivider(position);
            Divider hd = dividerLookup.getHorizontalDivider(position);
            outRect.left = vd == null ? 0 : vd.size; // 设置左边的偏移量
            outRect.bottom = hd == null ? 0 : hd.size; // 设置底部的偏移量
            if (spanIndex == 0) outRect.left = 0; // 如果是第一个列,不设置左边的偏移量
            if (lastSpanGroupIndex == spanGroupIndex) outRect.bottom = 0; // 如果是最后一行,不设置底部的偏移量
            return;
        }
        // 如果是LinearLayoutManager
        else if (layoutManager instanceof LinearLayoutManager) {
            Divider hd = dividerLookup.getHorizontalDivider(position);
            outRect.bottom = hd == null ? 0 : hd.size; // 设置底部的偏移量
            if (position == itemCount) outRect.bottom = 0; // 如果是最后一个项,不设置底部的偏移量
            return;
        }
        throw new IllegalArgumentException("It is not currently supported StaggeredGridLayoutManager"); // 抛出不支持StaggeredGridLayoutManager的异常
    }
}

设计的高明之处

1. 灵活的分隔线定义

  • 优势Divider类允许定义分隔线的颜色、大小和边距,提供了灵活性,可以根据不同的需求自定义分隔线的外观。
  • 实现:分隔线的属性被封装在Divider类中,可以通过DividerLookup接口灵活获取。

2. 支持多种布局管理器

  • 优势:支持GridLayoutManagerLinearLayoutManager,处理这两种常见的布局管理器,并能根据布局类型正确地设置偏移和绘制分隔线。

3. 可扩展性

  • 优势:通过DividerLookup接口,用户可以自定义分隔线获取逻辑,提供了很好的扩展性,可以根据不同的需求实现不同的分隔线样式。
  • 实现DividerLookup接口定义了获取竖直和水平分隔线的方法,用户可以实现该接口来定义自己的分隔线逻辑。

4. 清晰的代码结构

  • 优势:代码结构清晰,职责分明,每个方法和类都有明确的职责,便于阅读和维护。
  • 实现:通过将分隔线绘制和偏移设置逻辑分开,以及定义单一职责的类和接口,使得代码易于理解和扩展。

5. 详细的边界处理

  • 优势:在GridLayoutManager中,处理了第一列和最后一行的特殊情况,确保分隔线在边界处的正确显示。
  • 实现:通过检查spanIndexspanGroupIndex,判断是否为第一列或最后一行,从而避免在这些边界处绘制分隔线。

6. 高效的绘制方法

  • 优势:绘制分隔线的方法通过复用Paint对象,减少了资源的开销,提高了绘制的效率。
  • 实现:使用单一的Paint对象进行绘制,并在每次绘制前设置其颜色。

这些出色的特点使得这个自定义ItemDecoration在功能、灵活性和性能上都表现得非常优秀,适用于多种实际场景中的RecyclerView装饰需求。

设计的不足之处

1. 绘制性能优化

  • 问题:在onDraw中,每次绘制前都从DividerLookup获取分隔线对象,可能存在性能问题,尤其是在频繁滚动时。
  • 优化建议:可以缓存Divider对象,避免重复计算或查找。
public class DividerItemDecoration extends RecyclerView.ItemDecoration {

    private SparseArray<Divider> verticalDividerCache = new SparseArray<>();
    private SparseArray<Divider> horizontalDividerCache = new SparseArray<>();

    private Divider getVerticalDivider(int position) {
        Divider divider = verticalDividerCache.get(position);
        if (divider == null) {
            divider = dividerLookup.getVerticalDivider(position);
            verticalDividerCache.put(position, divider);
        }
        return divider;
    }

    private Divider getHorizontalDivider(int position) {
        Divider divider = horizontalDividerCache.get(position);
        if (divider == null) {
            divider = dividerLookup.getHorizontalDivider(position);
            horizontalDividerCache.put(position, divider);
        }
        return divider;
    }
}

2. 边界条件处理

  • 问题:在GridLayoutManager中,对第一列和最后一行的分隔线处理存在潜在问题,例如,如果网格布局有多个子项跨多列或多行,可能需要更加复杂的逻辑。
  • 优化建议:处理边界条件时,需要更复杂的逻辑来准确判断是否为第一列或最后一行。

3. StaggeredGridLayoutManager支持

  • 问题:当前代码不支持StaggeredGridLayoutManager
  • 优化建议:可以实现对StaggeredGridLayoutManager的支持,以便更全面地覆盖不同的布局管理器。

4 多样化支持

  • 问题:当前仅支持单一颜色和大小的分隔线,缺乏多样化的支持。
  • 优化建议:提供更灵活的API,支持不同类型的分隔线,如图片、渐变色等。

5. 不支持设置方向

  • 问题:当前代码不支持设置分隔线的方向,即无法控制是水平还是垂直方向的分隔线。
  • 优化建议:添加一个属性来控制分隔线的方向,并在getItemOffsets方法中处理。