在很早很早之前(long long ago),ListView鼎盛的时代有一个属性叫作divider。可是在RecycleView上面就是找不到他,那怎么办呢???直到后来有一天发现他变身了,变成了ItemDecoration。实在是扯不下去了,直接开始吧! 这篇博客酝酿了好长时间,但愿不会让各位看官失望。bash
了解ItemDecoration的原理,本身能够添加分割线,每一个 ItemView 上叠加一个角标,自定义 RecyclerView 中的头部或者是粘性头部。ide
class TextItemDecoration extends RecyclerView.ItemDecoration {
//设置ItemView的内嵌偏移长度(inset)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
// 在子视图上设置绘制范围,并绘制内容
// 绘制图层在ItemView如下,因此若是绘制区域与ItemView区域相重叠,会被遮挡
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
//一样是绘制内容,但与onDraw()的区别是:绘制在图层的最上层
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
}
复制代码
//设置ItemView的内嵌偏移长度(inset)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//只是添加下面这一行代码
outRect.set(50, 50, 50, 50);
}
复制代码
把这个·ItemDecoration·放在下面的RecyclerView上面,
代码以下: recyclerView2.addItemDecoration(new TextItemDecoration());
运行效果,以下图所示:源码分析
下面的代码都是在RecyclerView中,能够在RecyclerView里面找到源码:优化
/测量全部的子view的宽和高,获得这个子view的Rect。而后就能获得这一块真正的宽和高。
//注意还有padding的值。
public void measureChild(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
child.measure(widthSpec, heightSpec);
}
}
//获得每一个子view相应的Rect,
//mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
//上面的代码就是设置相应的四个值,就和咱们的TextItemDecoration类里面的代码对应起来了。
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}
if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
// changed/invalid items should not be updated until they are rebound.
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
//下面就是详细的赋值
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
复制代码
咱们先来看看咱们自定义的TextItemDecoration类里面的onDraw()代码,以下所示:ui
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
复制代码
很明显上面传递了一个Canvas 参数对象,因此它拥有了绘制的能力。this
注意: 1.getItemOffsets 是针对每个 ItemView,而 onDraw 方法倒是针对 RecyclerView 自己,因此在 onDraw 方法中须要遍历屏幕上可见的 ItemView,分别获取它们的位置信息,而后分别的绘制对应的分割线。 2.Itemdecoration的onDraw()绘制会先于ItemView的onDraw()绘制,spa
第二点会出现以下的状况:3d
出现上面的问题解决方案是getItemOffsets()与onDraw()一块使用。说的我一愣一愣的,最主要的问题onDrow()的时候,是怎样获得相应的点。code
英雄莫怕,请看上面的代码onDraw(Canvas c, RecyclerView parent, RecyclerView.State state):orm
看第二个参数:RecyclerView parent 这就是咱们的突破点。(这里有一个疑问点???咱们第4节处理。)
int childCount = parent.getChildCount();
View child = parent.getChildAt(i);
上面的代码就能解决咱们的问题。
复制代码
上代码实战:要实现的效果以下:
做者:yzzCool 连接:https://www.jianshu.com/p/41ae13016243 来源:简书 简书著做权归做者全部,任何形式的转载都请联系做者得到受权并注明出处。
代码实现以下:
class TextItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
public TextItemDecoration() {
this.mPaint = new Paint();
mPaint.setColor(Color.YELLOW);
// 画笔颜色设置为黄色
}
//设置ItemView的内嵌偏移长度(inset)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(50, 50, 50, 50);
}
// 在子视图上设置绘制范围,并绘制内容
// 绘制图层在ItemView如下,因此若是绘制区域与ItemView区域相重叠,会被遮挡
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 获取RecyclerView的Child view的个数
int childCount = parent.getChildCount();
// 遍历每一个Item,分别获取它们的位置信息,而后再绘制对应的分割线
for (int i = 0; i < childCount; i++) {
// 获取每一个Item的位置
final View child = parent.getChildAt(i);
// 设置矩形(分割线)的宽度为10px
final int mDivider = 10;
// 矩形左上顶点 = (ItemView的左边界,ItemView的下边界)
final int left = child.getLeft();
final int top = child.getBottom();
// 矩形右下顶点 = (ItemView的右边界,矩形的下边界)
final int right = child.getRight();
final int bottom = top + mDivider;
// 经过Canvas绘制矩形(分割线)
c.drawRect(left, top, right, bottom, mPaint);
}
}
//一样是绘制内容,但与onDraw()的区别是:绘制在图层的最上层
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
}
复制代码
咱们先来看看咱们自定义的TextItemDecoration类里面的onDrawOver()代码,以下所示:
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
复制代码
很明显onDrawOver里面的参数和onDraw里面的参数如出一辙,那还要onDrawOver有什么用呢???。请看下图:
class TextItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
private Bitmap bitmap;
public TextItemDecoration() {
this.mPaint = new Paint();
mPaint.setColor(Color.YELLOW);
// 画笔颜色设置为黄色
bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.email);
}
//设置ItemView的内嵌偏移长度(inset)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(50, 50, 50, 50);
}
// 在子视图上设置绘制范围,并绘制内容
// 绘制图层在ItemView如下,因此若是绘制区域与ItemView区域相重叠,会被遮挡
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 获取RecyclerView的Child view的个数
int childCount = parent.getChildCount();
// 遍历每一个Item,分别获取它们的位置信息,而后再绘制对应的分割线
for (int i = 0; i < childCount; i++) {
// 获取每一个Item的位置
final View child = parent.getChildAt(i);
// 设置矩形(分割线)的宽度为10px
final int mDivider = 10;
// 矩形左上顶点 = (ItemView的左边界,ItemView的下边界)
final int left = child.getLeft();
final int top = child.getBottom();
// 矩形右下顶点 = (ItemView的右边界,矩形的下边界)
final int right = child.getRight();
final int bottom = top + mDivider;
// 经过Canvas绘制矩形(分割线)
c.drawRect(left, top, right, bottom, mPaint);
}
}
//一样是绘制内容,但与onDraw()的区别是:绘制在图层的最上层
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
final int left = child.getRight() - bitmap.getWidth();
final int top = child.getTop();
c.drawBitmap(bitmap, left, top, mPaint);
}
}
}
复制代码
具体的思路以下图所示:
咱们的操做在OutRect里面处理。下面咱们用假数据处理,页面中只保留一个RecyclerView。咱们只分析TextItemDecoration里面的代码。代码以下:
class TextItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
public TextItemDecoration() {
this.mPaint = new Paint();
// 画笔颜色设置为黄色
mPaint.setColor(Color.YELLOW);
}
//设置ItemView的内嵌偏移长度(inset)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int position = parent.getChildAdapterPosition(view);
if (position % 5 == 0) {
outRect.set(0, 50, 0, 0);
}
}
// 在子视图上设置绘制范围,并绘制内容
// 绘制图层在ItemView如下,因此若是绘制区域与ItemView区域相重叠,会被遮挡
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 获取RecyclerView的Child view的个数
int childCount = parent.getChildCount();
// 遍历每一个Item,分别获取它们的位置信息,而后再绘制对应的分割线
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
int index = parent.getChildAdapterPosition(child);
if (index % 5 == 0) {
int left = 0;
int top = child.getTop() - 50;
int right = child.getRight();
int bottom = child.getTop();
c.drawRect(left, top, right, bottom, mPaint);
}
}
}
//一样是绘制内容,但与onDraw()的区别是:绘制在图层的最上层
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
}
复制代码
效果图以下:
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
// 获取RecyclerView的Child view的个数
int childCount = parent.getChildCount();
// 遍历每一个Item,分别获取它们的位置信息,而后再绘制对应的分割线
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
int index = parent.getChildAdapterPosition(child);
if (index % 5 == 0) {
int item = (index) / 5;
if (i < 5) {
if (i == 1 && child.getTop() < 100) {
int left = 0;
int top = child.getTop() - 100;
int right = child.getRight();
int bottom = child.getTop() - 50;
c.drawRect(left, top, right, bottom, mPaint);
c.drawText("这是条目" + item + "视图i是+" + i + "顶部的高低" + child.getTop() + "index++" + index, left, top + 50, textPaint);
} else {
int left = 0;
int top = 0;
int right = child.getRight();
int bottom = 50;
c.drawRect(left, top, right, bottom, mPaint);
if (i == 0) {
c.drawText("这是条目" + (item + 1) + "视图i是+" + i + "顶部的高低" + child.getTop() + "index++" + index, left, top + 50, textPaint);
} else {
c.drawText("这是条目" + (item) + "视图i是+" + i + "顶部的高低" + child.getTop() + "index++" + index, left, top + 50, textPaint);
}
}
}
if (i != 0) {
int left = 0;
int top = child.getTop() - 50;
int right = child.getRight();
int bottom = child.getTop();
c.drawRect(left, top, right, bottom, mPaint);
c.drawText("这是条目" + item + "视图i是+" + i + "顶部的高低" + child.getTop() + "index++" + index, left, top + 50, textPaint);
}
}
}
}
复制代码
咱们的效果图以下:
我把要打印的都给小伙伴们打印出来了。具体的优化看本身的需求优化就能够了。
本篇文章介绍了RecyclerView.ItemDecoration的使用,还有它的原理。其实仍是挺简单的。我相信简单的自定义小伙伴应该都会了。