前言
RecyclerView目前来说,是日常开发中使用最多的控件,功能强大而且复杂。而Item Decoration作为RecyclerView开发过程中不可或缺的部分,需要深入的了解一下。
源码分析
/**
* An ItemDecoration allows the application to add a special drawing and layout offset
* to specific item views from the adapter's data set. This can be useful for drawing dividers
* between items, highlights, visual grouping boundaries and more.
*
* <p>All ItemDecorations are drawn in the order they were added, before the item
* views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()}
* and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView,
* RecyclerView.State)}.</p>
*/
public abstract static class ItemDecoration {
/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn before the item views are drawn,
* and will thus appear underneath the views.
*
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView
*/
public void onDraw(Canvas c, RecyclerView parent, State state) {
onDraw(c, parent);
}
/**
* @deprecated
* Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}
*/
@Deprecated
public void onDraw(Canvas c, RecyclerView parent) {
}
/**
* Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
* Any content drawn by this method will be drawn after the item views are drawn
* and will thus appear over the views.
*
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView.
*/
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
onDrawOver(c, parent);
}
/**
* @deprecated
* Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}
*/
@Deprecated
public void onDrawOver(Canvas c, RecyclerView parent) {
}
/**
* @deprecated
* Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
*/
@Deprecated
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
outRect.set(0, 0, 0, 0);
}
/**
* Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
* the number of pixels that the item view should be inset by, similar to padding or margin.
* The default implementation sets the bounds of outRect to 0 and returns.
*
* <p>
* If this ItemDecoration does not affect the positioning of item views, it should set
* all four fields of <code>outRect</code> (left, top, right, bottom) to zero
* before returning.
*
* <p>
* If you need to access Adapter for additional data, you can call
* {@link RecyclerView#getChildAdapterPosition(View)} to get the adapter position of the
* View.
*
* @param outRect Rect to receive the output.
* @param view The child view to decorate
* @param parent RecyclerView this ItemDecoration is decorating
* @param state The current state of RecyclerView.
*/
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
parent);
}
}
注意看英文注释,ItemDecoration是用于装饰RecyclerView的Item的,主要关注如下三个方法
getItemOffsets
参数 | 意义 |
---|---|
outRect | ItemView的外围矩形 |
view | ItemView |
parent | RecyclerView,ItemView的Parent |
state | RecyclerView的状态 |
直接这样子说看不出效果,上几张图区分一下
outRect.top =20;
outRect.bottom =20;
outRect.left=60;
outRect.right =60;
outRect.top =20;
outRect.bottom =20;
outRect.left=60;
outRect.right =0;
outRect.top =60;
outRect.bottom =60;
outRect.left=20;
outRect.right =20;
看上去设置outRect设置top,left,right,bottom有一种padding的效果,但是实际上只是它是ItemView外围的空间,你可以将其理解成RecyclerView针对每个ItemView设置的Padding,而不是ItemView自身的padding。
onDraw
参数 | 意义 |
---|---|
c | RecyclerView的Canvas |
parent | RecyclerView,ItemView的Parent |
state | RecyclerView的状态 |
onDraw操作是在RecyclerView所在的画布上绘制内容,也就是在ItemView的背后绘制内容,重叠部分会被ItemView遮挡。
比如我们在Item View的背后画一个圆圈圈。
public YdDividerItemDecoration(){
mPaint = new Paint();
mPaint.setColor(Color.GREEN);
mPaint.setAntiAlias(true);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
Logger.d("yidong -- onDraw");
int width = parent.getWidth();
int height = parent.getHeight();
int radius = Math.min(width, height)/2;
c.drawCircle(width/2, height/2, radius, mPaint);
}
这个是为了说明,我们操作的画布是RecyclerView的画布,而不是针对ItemView,所以一般情况下,我们需要针对每个ItemView做操作,比如画分割线,官方给出的DividerItemDecoration的onDraw方法
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getLayoutManager() == null || mDivider == null) {
return;
}
if (mOrientation == VERTICAL) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
private void drawVertical(Canvas canvas, RecyclerView parent) {
canvas.save();
final int left;
final int right;
//noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
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(child.getTranslationY());
final int top = bottom - mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
canvas.restore();
}
可以看出都是便利RecyclerView的Item来做操作的。
onDrawOver
参数 | 意义 |
---|---|
c | RecyclerView的Canvas |
parent | RecyclerView,ItemView的Parent |
state | RecyclerView的状态 |
参数类型和意义和onDraw一模一样,但是效果确实在RecyclerView的ItemView之上。
还是换个圆圈圈,代码和上面一样,只是移动到onDrawOver方法下执行。
可以很明显的看到,我们的圆圈圈覆盖了ItemView。至于谁覆盖谁,显然是绘制顺序的问题。
可以看到执行顺序,onDrawOver是在onDraw之后的,但是ItemView的绘制是否在这个两个操作之前,从最后的效果来看,是的,但是需要代码证实。直接看RecyclerView绘制相关的方法即可。
@Override
public void draw(Canvas c) {
//这里是RecyclerView的绘制过程,和View的绘制过程类似,先是背景,然后是调用
//recyclerview的onDraw方法绘制自身,然后通过dispatchDraw来绘制子View
// 而onDraw(下面)执行的就是ItemDecoration的onDraw方法,所以顺序很明显了。
//ItemDecoration-> ItemView -> ItemDecoration.onDrawOver
super.draw(c);
// 执行ItemDecoration onDrawOver方法,最顶层
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
……
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
// 执行ItemDecoration的onDraw方法,最底层
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
总结
看清楚了ItemDecoration的实现原理,具体通过什么方式绘制出什么样的图形,应该只是设计和时间的问题,Github上有不错的一些实现,大家有空可以去学习借鉴一下。