1. 引言
在Android开发中,RecyclerView是一个非常常用的组件,用于显示大量数据项。为了使数据项之间的视觉分隔更加清晰,我们通常会使用分隔线。ChoicesWang/RecyclerView_Divider是一个开源库,提供了一个自定义的ItemDecoration实现,用于在RecyclerView中绘制分隔线。 本篇文章将介绍ChoicesWang/RecyclerView_Divider库,并深入分析其源码实现。
2. 库的介绍
ChoicesWang/RecyclerView_Divider库提供了一个高度灵活且易于使用的分隔线实现。
用户可以自定义分隔线的颜色、大小和边距,并支持多种布局管理器,包括GridLayoutManager和LinearLayoutManager。通过实现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. 支持多种布局管理器
- 优势:支持
GridLayoutManager和LinearLayoutManager,处理这两种常见的布局管理器,并能根据布局类型正确地设置偏移和绘制分隔线。
3. 可扩展性
- 优势:通过
DividerLookup接口,用户可以自定义分隔线获取逻辑,提供了很好的扩展性,可以根据不同的需求实现不同的分隔线样式。 - 实现:
DividerLookup接口定义了获取竖直和水平分隔线的方法,用户可以实现该接口来定义自己的分隔线逻辑。
4. 清晰的代码结构
- 优势:代码结构清晰,职责分明,每个方法和类都有明确的职责,便于阅读和维护。
- 实现:通过将分隔线绘制和偏移设置逻辑分开,以及定义单一职责的类和接口,使得代码易于理解和扩展。
5. 详细的边界处理
- 优势:在
GridLayoutManager中,处理了第一列和最后一行的特殊情况,确保分隔线在边界处的正确显示。 - 实现:通过检查
spanIndex和spanGroupIndex,判断是否为第一列或最后一行,从而避免在这些边界处绘制分隔线。
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方法中处理。