RecyclerView 并没有 divider 属性,但是我们可以通过 RecyclerView 的 addItemDecoration() 来添加分割线,该方法参数为 RecyclerView.ItemDecoration。
介绍 当 RecyclerView 添加 ItemDecoration 后,RecyclerView 在绘制每个 item 的时候,会去绘制 decorator,也就是会调用 ItemDecoration 的 onDraw() 和 onDrawOver() 方法。
RecyclerView.ItemDecoration 是抽象类,主要提供三个方法:
onDraw(Canvas c, RecyclerView parent, State state): 在绘制item(drawChild) 前调用
onDrawOver(Canvas c, RecyclerView parent, State state): 在绘制item(drawChild) 后调用
getItemOffsets(Rect outRect, View view, RecyclerView parent, State state):outRect设置 item 的偏移量,用于绘制 decorator(也就是divider)
关于 getItemOffsets 函数: RecyclerView 添加分割线,实际上就是 RecyclerView 的 item 之间添加了用作分割线的View,自然而然后续的 item 就会有偏移量,所以用 getItemOffsets 中的 outRect 来保存 item 的偏移量,从而便于绘制 decorator。
实现 实际上在当前版本的 RecyclerView (25.3.1)
中已经有 ItemDecoration
关于分割线的默认实现类 DividerItemDecoration
:
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 package android.support.v7.widget;import android.annotation.SuppressLint;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Rect;import android.graphics.drawable.Drawable;import android.support.annotation.NonNull;import android.support.v4.view.ViewCompat;import android.view.View;import android.widget.LinearLayout;public class DividerItemDecoration extends RecyclerView .ItemDecoration { public static final int HORIZONTAL = LinearLayout.HORIZONTAL; public static final int VERTICAL = LinearLayout.VERTICAL; private static final int [] ATTRS = new int []{ android.R.attr.listDivider }; private Drawable mDivider; private int mOrientation; private final Rect mBounds = new Rect(); public DividerItemDecoration (Context context, int orientation) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0 ); a.recycle(); setOrientation(orientation); } public void setOrientation (int orientation) { if (orientation != HORIZONTAL && orientation != VERTICAL) { throw new IllegalArgumentException( "Invalid orientation. It should be either HORIZONTAL or VERTICAL" ); } mOrientation = orientation; } public void setDrawable (@NonNull Drawable drawable) { if (drawable == null ) { throw new IllegalArgumentException("Drawable cannot be null." ); } mDivider = drawable; } @Override public void onDraw (Canvas c, RecyclerView parent, RecyclerView.State state) { if (parent.getLayoutManager() == null ) { return ; } if (mOrientation == VERTICAL) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } } @SuppressLint ("NewApi" ) private void drawVertical (Canvas canvas, RecyclerView parent) { canvas.save(); final int left; final int right; if (parent.getClipToPadding()) { left = parent.getPaddingLeft(); right = parent.getWidth() - parent.getPaddingRight(); canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom()); } else { left = 0 ; right = parent.getWidth(); } final int childCount = parent.getChildCount(); for (int i = 0 ; i < childCount; i++) { final View child = parent.getChildAt(i); parent.getDecoratedBoundsWithMargins(child, mBounds); final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child)); final int top = bottom - mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } canvas.restore(); } @SuppressLint ("NewApi" ) private void drawHorizontal (Canvas canvas, RecyclerView parent) { canvas.save(); final int top; final int bottom; if (parent.getClipToPadding()) { top = parent.getPaddingTop(); bottom = parent.getHeight() - parent.getPaddingBottom(); canvas.clipRect(parent.getPaddingLeft(), top, parent.getWidth() - parent.getPaddingRight(), bottom); } else { top = 0 ; bottom = parent.getHeight(); } final int childCount = parent.getChildCount(); for (int i = 0 ; i < childCount; i++) { final View child = parent.getChildAt(i); parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); final int right = mBounds.right + Math.round(ViewCompat.getTranslationX(child)); final int left = right - mDivider.getIntrinsicWidth(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } canvas.restore(); } @Override public void getItemOffsets (Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if (mOrientation == VERTICAL) { outRect.set(0 , 0 , 0 , mDivider.getIntrinsicHeight()); } else { outRect.set(0 , 0 , mDivider.getIntrinsicWidth(), 0 ); } } }
在代码中添加:
1 recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(), mLayoutManager.getOrientation()));
就有了分割线。 默认的分割线效果是系统自带的 listDivider
的效果,我们也可以在主题配置文件中自定义全局的分割线,或者调用 setDivider
为每个 RecyclerView
设置单独的分割线。
网络流行代码存在的问题 目前好多博客中关于 DividerItemDecoration
中 drawVertical()
和 drawHorizontal()
方法与官方的方法其实是有出入的:
public void drawVertical (Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0 ; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal (Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0 ; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
注释中说的比较明确了,网上好多方法主要的问题在于 没有考虑 clipToPadding 属性值 。 clipToPadding
表示控件的绘制区域是否在 padding 区域外面,其默认值为 true
。比如,垂直方向的 RecyclerView,当 clipToPadding=false
时,其初始绘制区域与 padding 值有关,但向上滑动时,RecylerView 的 item 会滑到 padding 区域里面。
下面用示意图来进行解释,RecyclerView 的 paddingTop = 40dp
, clipToPadding = false
, 下图中白色区域为 paddingTop
区域:
小结 总的来说,目前添加分割线只需要使用 recyclerview-v7
包下的 DividerItemDecoration
类即可,分割线可以通过 setDivider 来个性化指定,也可以通过配置主题中的 android:listDivider
来全局指定。
参考 Android RecyclerView 使用完全解析 体验艺术般的控件 RecyclerView系列之二:添加分隔线 android:clipToPadding和android:clipChildren