先上效果:

andorid使用ItemDecoration绘制超炫酷标签_i++


左上角右上角的标签在开发中可能会用到,其实最初的时候是因为我一直都没有用ItemDecoration,个人不喜欢,太麻烦,还不如直接画出来,然后最下面一条隐藏底部分割线呢,用这东西看不到效果,调试时间会变长。但偶然间看了一篇博客,讲了除了这一用途外,可以用来做标签,这篇博客在:

​https://www.jianshu.com/p/b46a4ff7c10a​

此文章有截图如下:

andorid使用ItemDecoration绘制超炫酷标签_android_02


这个效果给了我一定的启发,ItemDecoration和adapter相比也有自己的优势,那就是入侵性低,既然这篇文说了可以用来做标签,那就做个例子试一下:

Activity代码:

public class DecorationActivity extends AppCompatActivity

private RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_decoration);

recyclerView = (RecyclerView) findViewById(R.id.recycler_decoration);

initData();
MyRecyclerAdapter recycleAdapter= new MyRecyclerAdapter(DecorationActivity.this , mDatas );
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
//设置布局管理器
recyclerView.setLayoutManager(layoutManager);
//设置为垂直布局,这也是默认的
layoutManager.setOrientation(OrientationHelper.VERTICAL);
//设置Adapter
recyclerView.setAdapter( recycleAdapter);
//设置增加或删除条目的动画
recyclerView.setItemAnimator(new DefaultItemAnimator());
//距离
// recyclerView.addItemDecoration(new SimplePaddingDecoration(this));
recyclerView.addItemDecoration(new SimpleDividerDecoration(this));
recyclerView.addItemDecoration(new LeftAndRightTagDecoration(this));
// recyclerView.addItemDecoration(new LinearLayoutColorDivider(getResources(),R.color.white,R.dimen.divider_height,LinearLayoutManager.VERTICAL));
}

private List mDatas;
private void initData() {
mDatas = new ArrayList<String>();
for ( int i=0; i < 40; i++) {
mDatas.add( "item"+i);
}
}
}

LeftAndRightTagDecoration:

public class LeftAndRightTagDecoration extends RecyclerView.ItemDecoration
private int tagWidth;
private Paint leftPaint;
private Paint rightPaint;
private Paint textPaint;
public LeftAndRightTagDecoration(Context context) {
leftPaint = new Paint();
leftPaint.setColor(context.getResources().getColor(R.color.colorAccent));
rightPaint = new Paint();
rightPaint.setColor(context.getResources().getColor(R.color.colorPrimary));
textPaint = new Paint();
textPaint.setColor(context.getResources().getColor(R.color.white));
textPaint.setStrokeWidth(1);
textPaint.setTextSize(20);
textPaint.setTextAlign(Paint.Align.LEFT);
tagWidth = context.getResources().getDimensionPixelSize(R.dimen.tag_width);
}

@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);
int pos = parent.getChildAdapterPosition(child);
boolean isLeft = pos % 2 == 0;
if (isLeft) {
drawLeftTriangle(c,child);
drawLeftTag(c,child);

} else {
drawRightTriangle(c,child);
drawRightTag(c,child);
}
}
}

private void drawRightTag(Canvas c, View child) {
float top = child.getTop();
String testString = "特价";

Rect bounds = new Rect();
textPaint.getTextBounds(testString, 0, testString.length(), bounds);

c.save();
c.translate(c.getWidth(),top);
c.rotate(45);
c.translate(-tagWidth/1.414f,0);

float rectWidth = tagWidth*1.414f;
float rectHeight = tagWidth/1.414f;
float oldX = rectWidth/2 - bounds.width()/2;
float oldY = rectHeight/2 + bounds.height()/2;
c.drawText(testString, oldX, oldY, textPaint);
c.restore();
}

private void drawRightTriangle(Canvas c, View child) {
float right = child.getRight();
float top = child.getTop();
Path path= new Path();
path.moveTo(right, top);
path.lineTo(right-tagWidth, top);
path.lineTo(right, tagWidth+top);
path.close();
c.drawPath(path, rightPaint);
}

private void drawLeftTag(Canvas c, View child) {
float top = child.getTop();
String testString = "特价";

Rect bounds = new Rect();
textPaint.getTextBounds(testString, 0, testString.length(), bounds);

c.save();
c.translate(0,top);
c.rotate(-45);
c.translate(-tagWidth/1.414f,0);

float rectWidth = tagWidth*1.414f;
float rectHeight = tagWidth/1.414f;
float oldX = rectWidth/2 - bounds.width()/2;
float oldY = rectHeight/2 + bounds.height()/2;
c.drawText(testString, oldX, oldY, textPaint);
c.restore();
}

private void drawLeftTriangle(Canvas c, View child) {
float left = child.getLeft();
float top = child.getTop();
Path path= new

分割线的Decoration:

public class SimpleDividerDecoration extends RecyclerView.ItemDecoration

private int dividerHeight;
private Paint dividerPaint;

public SimpleDividerDecoration(Context context) {
dividerPaint = new Paint();
dividerPaint.setColor(context.getResources().getColor(R.color.black));
dividerHeight = context.getResources().getDimensionPixelSize(R.dimen.divider_height);
}


@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
}

@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int childCount = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();

for (int i = 0; i < childCount-1; i++) {
View view = parent.getChildAt(i);
float top = view.getBottom();
float

代码很清楚,在左边的时候,就绘制左边的三角和左边的标签,右边的时候就绘制右边的三角,右边的标签。主要讲一下文字的绘制思路。

1.学会特定方形居中绘制:

String testString = "特价";
Rect bounds = new Rect();
textPaint.getTextBounds(testString, 0, testString.length(), bounds);
loat oldX = rectWidth/2 - bounds.width()/2;
float oldY = rectHeight/2 + bounds.height()/2;
c.drawText(testString, oldX, oldY, textPaint);

这里的宽度是你要绘制的矩形在其内的宽度,这里的高度是你要绘制在其内的矩形高度

可以参考:下图中橙色区域是你要绘制的矩形

andorid使用ItemDecoration绘制超炫酷标签_android_03

2.了解c.translate(a,b);c.rotate(c);c.save();c.restore();原理。

  • c.translate(a,b);
    这句表示移动坐标系到哪个位置,举个栗子:

andorid使用ItemDecoration绘制超炫酷标签_标签_04


打叉的地方坐标为(10,5),然后我们c.translate(10,5),坐标系移动到这里,变成:

andorid使用ItemDecoration绘制超炫酷标签_分割线_05


坐标系平移到该位置,原来在(10,5)的点变成(0,0)

  • c.rotate(c);
    这句代表坐标系旋转角度
  • andorid使用ItemDecoration绘制超炫酷标签_分割线_06

  • 红色矩形为黑色矩形旋转c度得到,注意此处c.ratate(c);括号里面的c是旋转角度不是弧度。
    举个例子,原某点坐标为(3,3),c.rotate(45);后,此点新坐标为(0,3倍根号2)大约是(0,4.242)

c.save();
因为进行translate,rotate操作会改变坐标系,为了不给其他的点造成影响,需要使用c.save()在进行这些破坏坐标系操作前先保存旧的画布。
c.restore();
进行完对旋转平移坐标系的系列操作后,不继续使用了,则进行c.restore()恢复之前c.save()保存的坐标系。

3.标签文字绘制原理讲解

好了,下面讲解标签文字画法,以左边标签为例,右边同理:

.getTop();
String testString = "特价";

Rect bounds = new Rect();
textPaint.getTextBounds(testString, 0, testString.length(), bounds);

c.save();
c.translate(0,top);
c.rotate(-45);
c.translate(-tagWidth/1.414f,0);

float rectWidth = tagWidth*1.414f;
float rectHeight = tagWidth/1.414f;
float oldX = rectWidth/2 - bounds.width()/2;
float oldY = rectHeight/2 + bounds.height()/2;
c.drawText(testString, oldX, oldY, textPaint);
c.restore();

下面先看个图:

andorid使用ItemDecoration绘制超炫酷标签_ide_07


红色部分代表标签,ox2y2我期望移动到的新的坐标系。橙色加红色为字的居中长方形部分,我期望文字在此部分居中。

首先,需要把坐标移动到oxy的位置。因为除了第一行,其他行的坐标并非此位置,所以

c.translate(0,top);

假设标签的直角边为a。首先需要逆时针旋转45度使得新坐标ox1y1的x1横坐标与x2重合,y1纵坐标与y2平行,然后需要反向移动a/根号2(根据勾股定理),根号2约为1.414

andorid使用ItemDecoration绘制超炫酷标签_分割线_08


于是:

c.rotate(-45);
c.translate(-tagWidth/1.414f,0);

然后注意了,标签的斜边为a*根号2,橙色的直角边为a/根号2,所以得到包含标签字的外部矩形长为a*根号2,宽为a/根号2.绘制即可。当然这样绘制看上去似乎比较靠近三角形的直角,可以通过修改rectHeight 来进行调整:

float rectWidth = tagWidth*1.414f;
float rectHeight = tagWidth/1.414f;
rectHeight = rectHeight/3*4;//将标签调整到rectHeight2/3的位置
float oldX = rectWidth/2 - bounds.width()/2;
float oldY = rectHeight/2 + bounds.height()/2;
c.drawText(testString, oldX, oldY, textPaint);

过程是这样的,需要调整到矩形高度的2/3,那么可以扩大矩形为原来的4/3(居中则显示在2/3),调整一下字的大小,大约可以在32,可以显示出不错的效果:

andorid使用ItemDecoration绘制超炫酷标签_标签_09

添加阴影

上文中没提到阴影,因为到这行字以上,我的阴影还没调整好。
左标签加阴影:

private int shadeWidth = 15;
private void drawLeftTriangle(Canvas c, View child) {
float left = child.getLeft();
float top = child.getTop();
Path path= new Path();
path.moveTo(left, top);
path.lineTo(tagWidth+left, top);
path.lineTo(left, tagWidth+top);
path.close();
Shader mShader = new LinearGradient(left+tagWidth/2,top+tagWidth/2,left+(tagWidth+shadeWidth)/2,top+(tagWidth+shadeWidth)/2,new int[] {0x66000000,Color.TRANSPARENT},null,Shader.TileMode.CLAMP);

Path path2= new Path();
path2.moveTo(left, top);
path2.lineTo(tagWidth+left+shadeWidth, top);
path2.lineTo(left, tagWidth+top+shadeWidth);
path2.close();
leftPaint.setShader(mShader);
c.drawPath(path2, leftPaint);
leftPaint.setShader(null);
c.drawPath(path, leftPaint);
}

右标签加阴影:

.getRight();
float top = child.getTop();
Path path= new Path();
path.moveTo(right, top);
path.lineTo(right-tagWidth, top);
path.lineTo(right, tagWidth+top);
path.close();
Shader mShader = new LinearGradient(right-tagWidth/2,top+tagWidth/2,right-(tagWidth+shadeWidth)/2,top+(tagWidth+shadeWidth)/2,new int[] {0x66000000,Color.TRANSPARENT},null,Shader.TileMode.CLAMP);

Path path2= new Path();
path2.moveTo(right, top);
path2.lineTo(right-tagWidth-shadeWidth, top);
path2.lineTo(right, tagWidth+shadeWidth+top);
path2.close();
rightPaint.setShader(mShader);
c.drawPath(path2, rightPaint);
rightPaint.setShader(null);
c.drawPath(path, rightPaint);

原理是这样的:先画阴影(使用渐变),再画标签,这样就呈现出阴影在下的效果。所以阴影要比标签要大一点,此处的阴影宽度不是真正的阴影宽度,真正阴影宽度为给出代码使用阴影宽度除以根号2.

andorid使用ItemDecoration绘制超炫酷标签_android_10


如图,假设红色部分为标签部分,蓝色部分为阴影部分,红色部分宽度为a,蓝色部分横向宽度为b。渐变起点为A,终点为B,可得出渐变起点终点。当然,你也可以选用任何一条和A,B平行的有向线段做起点终点,比如A1(a,0),B1(a+b/2,b/2)也是可以的。

再次提醒,绘制阴影的范围要比标签大!当然,因为此处阴影只有一个方向,也可以选择绘制上图的蓝色梯形部分。

完成后如图所示:

andorid使用ItemDecoration绘制超炫酷标签_ide_11