前言: 在Android开发中,经常会遇到给某条数据右上角添加一个角标,来突出重点。实现这种角标做法有两种:1. 简单粗暴的做法就是让UI直接切图即可。2. 自定义控件来实现
1. 控件概述
CornerLabelView
是一个用于在视图的四个角落显示角标的自定义控件。角标可以显示文字,并且可以自定义角标的位置、大小、颜色、文字大小、文字颜色等属性。
2. 控件拆分
为了便于理解和维护,我们将 CornerLabelView 拆分为以下几个部分:
- 属性定义与初始化
- 测量与布局
- 绘制角标背景
- 绘制文字
- 属性设置方法
3. 属性定义与初始化
首先,我们需要定义控件的属性,并在构造函数中进行初始化
public class CornerLabelView extends View {
private int mHalfWidth; // View宽度的一半
private Paint mPaint; // 角标画笔
private TextPaint mTextPaint; // 文字画笔
private Path mPath; // 角标路径
private int position; // 角标位置,0:右上角、1:右下角、2:左下角、3:左上角
private int sideLength; // 角标的显示边长
private int textSize; // 字体大小
private int textColor; // 字体颜色
private String text; // 角标文字
private int bgColor; // 角标背景颜色
private int marginLeanSide; // 文字到斜边的距离
public CornerLabelView(Context context) {
this(context, null);
}
public CornerLabelView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
init();
}
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CornerLabelView, 0, 0);
for (int i = 0; i < ta.getIndexCount(); i++) {
int attr = ta.getIndex(i);
if (attr == R.styleable.CornerLabelView_position) {
position = ta.getInt(attr, 0);
} else if (attr == R.styleable.CornerLabelView_side_length) {
sideLength = ta.getDimensionPixelSize(attr, sideLength);
} else if (attr == R.styleable.CornerLabelView_text_size) {
textSize = ta.getDimensionPixelSize(attr, textSize);
} else if (attr == R.styleable.CornerLabelView_text_color) {
textColor = ta.getColor(attr, textColor);
} else if (attr == R.styleable.CornerLabelView_text) {
text = ta.getString(attr);
} else if (attr == R.styleable.CornerLabelView_bg_color) {
bgColor = ta.getColor(attr, bgColor);
} else if (attr == R.styleable.CornerLabelView_margin_lean_side) {
marginLeanSide = ta.getDimensionPixelSize(attr, marginLeanSide);
}
}
ta.recycle();
}
private void init() {
mPath = new Path();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(bgColor);
mTextPaint = new TextPaint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(textColor);
mTextPaint.setTextSize(textSize);
}
}
注意事项:
需要在res下的values中新建attrs.xml,来自定义属性
<declare-styleable name="CornerLabelView">
<attr name="side_length" format="dimension" />
<attr name="margin_lean_side" format="dimension" />
<attr name="bg_color" format="color" />
<attr name="text_color" format="color" />
<attr name="text_size" format="dimension" />
<attr name="text" format="string" />
<attr name="position">
<enum name="right_top" value="0" />
<enum name="right_bottom" value="1" />
<enum name="left_bottom" value="2" />
<enum name="left_top" value="3" />
</attr>
</declare-styleable>
4. 测量与布局
在 onMeasure
方法中,我们根据控件的宽高模式来设置控件的尺寸
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(sideLength * 2, sideLength * 2);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(heightSpecSize, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, widthSpecSize);
} else if (widthSpecSize != heightSpecSize) {
int size = Math.min(widthSpecSize, heightSpecSize);
setMeasuredDimension(size, size);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mHalfWidth = Math.min(w, h) / 2;
}
5. 绘制角标背景
在 onDraw
方法中,我们首先绘制角标的背景。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 将原点移动到画布中心
canvas.translate(mHalfWidth, mHalfWidth);
// 根据角标位置旋转画布
canvas.rotate(position * 90);
if (sideLength > mHalfWidth * 2) {
sideLength = mHalfWidth * 2;
}
// 绘制角标背景
mPath.moveTo(-mHalfWidth, -mHalfWidth);
mPath.lineTo(sideLength - mHalfWidth, -mHalfWidth);
mPath.lineTo(mHalfWidth, mHalfWidth - sideLength);
mPath.lineTo(mHalfWidth, mHalfWidth);
mPath.close();
canvas.drawPath(mPath, mPaint);
}
6. 绘制文字
接下来,我们绘制角标中的文字
// 绘制文字前画布旋转45度
canvas.rotate(45);
// 角标实际高度
int h1 = (int) (Math.sqrt(2) / 2.0 * sideLength);
int h2 = (int) -(mTextPaint.ascent() + mTextPaint.descent());
// 文字绘制坐标
int x = (int) -mTextPaint.measureText(text) / 2;
int y;
if (marginLeanSide >= 0) {
// 使用clv:margin_lean_side属性时
if (position == 1 || position == 2) {
if (h1 - (marginLeanSide - mTextPaint.ascent()) < (h1 - h2) / 2) {
y = -(h1 - h2) / 2;
} else {
y = (int) -(h1 - (marginLeanSide - mTextPaint.ascent()));
}
} else {
if (marginLeanSide < mTextPaint.descent()) {
marginLeanSide = (int) mTextPaint.descent();
}
if (marginLeanSide > (h1 - h2) / 2) {
marginLeanSide = (h1 - h2) / 2;
}
y = -marginLeanSide;
}
} else {
// 默认情况下
if (sideLength > mHalfWidth) {
sideLength = mHalfWidth;
}
y = (int) (-Math.sqrt(2) / 2.0 * sideLength + h2) / 2;
}
// 如果角标在右下、左下则进行画布平移、翻转,已解决绘制的文字显示问题
if (position == 1 || position == 2) {
canvas.translate(0, (float) (-Math.sqrt(2) / 2.0 * sideLength));
canvas.scale(-1, -1);
}
// 绘制文字
canvas.drawText(text, x, y, mTextPaint);
7. 属性设置方法
最后,我们提供了一些方法,用于动态设置控件的属性
public CornerLabelView setBgColorId(int bgColorId) {
this.bgColor = getResources().getColor(bgColorId);
mPaint.setColor(bgColor);
invalidate();
return this;
}
public CornerLabelView setBgColor(int bgColor) {
mPaint.setColor(bgColor);
invalidate();
return this;
}
public CornerLabelView setTextColorId(int colorId) {
this.textColor = getResources().getColor(colorId);
mTextPaint.setColor(textColor);
invalidate();
return this;
}
public CornerLabelView setTextColor(int color) {
mTextPaint.setColor(color);
invalidate();
return this;
}
public CornerLabelView setText(int textId) {
this.text = getResources().getString(textId);
invalidate();
return this;
}
public CornerLabelView setText(String text) {
this.text = text;
invalidate();
return this;
}
8. 总结
通过以上拆分,我们将 CornerLabelView
的实现分为了属性定义与初始化、测量与布局、绘制角标背景、绘制文字以及属性设置方法五个部分。这种拆分方式不仅使代码更易于理解和维护,也方便我们在后续的开发中进行扩展和修改。以下是整个类的全部代码实现过程
public class CornerLabelView extends View {
private int mHalfWidth;//View宽度的一半
private Paint mPaint;//角标画笔
private TextPaint mTextPaint;//文字画笔
private Path mPath;//角标路径
private int position;//角标位置,0:右上角、1:右下角、2:左下角、3:左上角
//角标的显示边长
private int sideLength = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());
//字体大小
private int textSize = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 14, getResources().getDisplayMetrics());
//字体颜色
private int textColor = Color.WHITE;
private String text;
//角标背景
private int bgColor = Color.RED;
//文字到斜边的距离
private int marginLeanSide = -1;
public CornerLabelView(Context context) {
this(context, null);
}
public CornerLabelView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
init();
}
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CornerLabelView, 0, 0);
for (int i = 0; i < ta.getIndexCount(); i++) {
int attr = ta.getIndex(i);
if (attr == R.styleable.CornerLabelView_position) {
position = ta.getInt(attr, 0);
} else if (attr == R.styleable.CornerLabelView_side_length) {
sideLength = ta.getDimensionPixelSize(attr, sideLength);
} else if (attr == R.styleable.CornerLabelView_text_size) {
textSize = ta.getDimensionPixelSize(attr, textSize);
} else if (attr == R.styleable.CornerLabelView_text_color) {
textColor = ta.getColor(attr, textColor);
} else if (attr == R.styleable.CornerLabelView_text) {
text = ta.getString(attr);
} else if (attr == R.styleable.CornerLabelView_bg_color) {
bgColor = ta.getColor(attr, bgColor);
} else if (attr == R.styleable.CornerLabelView_margin_lean_side) {
marginLeanSide = ta.getDimensionPixelSize(attr, marginLeanSide);
}
}
ta.recycle();
}
private void init() {
mPath = new Path();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(bgColor);
mTextPaint = new TextPaint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(textColor);
mTextPaint.setTextSize(textSize);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(sideLength * 2, sideLength * 2);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(heightSpecSize, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, widthSpecSize);
} else if (widthSpecSize != heightSpecSize) {
int size = Math.min(widthSpecSize, heightSpecSize);
setMeasuredDimension(size, size);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mHalfWidth = Math.min(w, h) / 2;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//将原点移动到画布中心
canvas.translate(mHalfWidth, mHalfWidth);
//根据角标位置旋转画布
canvas.rotate(position * 90);
if (sideLength > mHalfWidth * 2) {
sideLength = mHalfWidth * 2;
}
//绘制角标背景
mPath.moveTo(-mHalfWidth, -mHalfWidth);
mPath.lineTo(sideLength - mHalfWidth, -mHalfWidth);
mPath.lineTo(mHalfWidth, mHalfWidth - sideLength);
mPath.lineTo(mHalfWidth, mHalfWidth);
mPath.close();
canvas.drawPath(mPath, mPaint);
//绘制文字前画布旋转45度
canvas.rotate(45);
//角标实际高度
int h1 = (int) (Math.sqrt(2) / 2.0 * sideLength);
int h2 = (int) -(mTextPaint.ascent() + mTextPaint.descent());
//文字绘制坐标
int x = (int) -mTextPaint.measureText(text) / 2;
int y;
if (marginLeanSide >= 0) {
//使用clv:margin_lean_side属性时
if (position == 1 || position == 2) {
if (h1 - (marginLeanSide - mTextPaint.ascent()) < (h1 - h2) / 2) {
y = -(h1 - h2) / 2;
} else {
y = (int) -(h1 - (marginLeanSide - mTextPaint.ascent()));
}
} else {
if (marginLeanSide < mTextPaint.descent()) {
marginLeanSide = (int) mTextPaint.descent();
}
if (marginLeanSide > (h1 - h2) / 2) {
marginLeanSide = (h1 - h2) / 2;
}
y = -marginLeanSide;
}
} else {
//默认情况下
if (sideLength > mHalfWidth) {
sideLength = mHalfWidth;
}
y = (int) (-Math.sqrt(2) / 2.0 * sideLength + h2) / 2;
}
//如果角标在右下、左下则进行画布平移、翻转,已解决绘制的文字显示问题
if (position == 1 || position == 2) {
canvas.translate(0, (float) (-Math.sqrt(2) / 2.0 * sideLength));
canvas.scale(-1, -1);
}
//绘制文字
canvas.drawText(text, x, y, mTextPaint);
}
/**
* 设置角标背景色
*
* @param bgColorId
* @return
*/
public CornerLabelView setBgColorId(int bgColorId) {
this.bgColor = getResources().getColor(bgColorId);
mPaint.setColor(bgColor);
invalidate();
return this;
}
/**
* 设置角标背景色
*
* @param bgColor
* @return
*/
public CornerLabelView setBgColor(int bgColor) {
mPaint.setColor(bgColor);
invalidate();
return this;
}
/**
* 设置文字颜色
*
* @param colorId
* @return
*/
public CornerLabelView setTextColorId(int colorId) {
this.textColor = getResources().getColor(colorId);
mTextPaint.setColor(textColor);
invalidate();
return this;
}
/**
* 设置文字颜色
*
* @param color
* @return
*/
public CornerLabelView setTextColor(int color) {
mTextPaint.setColor(color);
invalidate();
return this;
}
/**
* 设置文字
*
* @param textId
* @return
*/
public CornerLabelView setText(int textId) {
this.text = getResources().getString(textId);
invalidate();
return this;
}
/**
* 设置文字
*
* @param text
* @return
*/
public CornerLabelView setText(String text) {
this.text = text;
invalidate();
return this;
}
}
CornerLabelView相关方法
方法名 | 描述 |
---|---|
setBgColorId(int bgColorId) | 设置角标背景色(资源id) |
setBgColor(int bgColor) | 设置角标背景色 |
setTextColorId(int colorId) | 设置文字颜色(资源id) |
setTextColor(int color) | 设置文字颜色 |
setText(int textId) | 设置文字(资源id) |
setText(String text) | 设置文字 |
CornerLabelView的自定义属性
属性名 | 格式 | 描述 | 默认值 |
---|---|---|---|
side_length | dimension | 角标水平显示宽度(当该值和CornerLabelView宽度相等时,角标显示为三角形) | 40dp |
bg_color | color | 角标背景色 | RED |
text_color | color | 文字颜色 | WHITE |
text_size | dimension | 文字尺寸 | 14sp |
text | string | 文字 | |
position | enum | 角标位置:right_top、right_bottom、left_bottom、left_top | right_top |
margin_lean_side | dimension | 文字到角最长标斜边的距离(因为有默认距离,则此属性可选) | -1 |