自定义label组件
支持边框绘制
支持shape背景(按指定圆角裁剪,矩形,圆角矩,圆形),支持指定角圆角
支持自定义阴影(颜色,偏移,深度)
边框颜色支持状态选择器
预览
核心绘制辅助类
public class LabelHelper {
private final Paint paint;
private Paint shadowPaint;
private final float[] radiusList = new float[8];// 矩阵四角圆角 两个一组分别为一角的x轴半径y轴半径 四组分别为 上左 上右 下右 下左
private final Rect rect;
private final Path path;
private final RectF rectF;
private float strokeWidth;
private ColorStateList strokeColor;
private boolean hasRadius;//是否有圆角
private int shadowColor;//是否有阴影
private float shadowRadius;
private float shadowDx;
private float shadowDy;
private Float contentInsetLeft = null;
private Float contentInsetRight = null;
private int defStrokeColor;
LabelHelper(View view, Context context, AttributeSet attrs) {
this(view, context, attrs, Color.TRANSPARENT);
}
LabelHelper(View view, Context context, AttributeSet attrs, @ColorInt int defStrokeColor) {
this.defStrokeColor = defStrokeColor;
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LabelView);
// 圆角
float radius = array.getDimension(R.styleable.LabelView_android_radius, 0);
float topLeftRadius = array.getDimension(R.styleable.LabelView_android_topLeftRadius, radius);
float topRightRadius = array.getDimension(R.styleable.LabelView_android_topRightRadius, radius);
float bottomLeftRadius = array.getDimension(R.styleable.LabelView_android_bottomLeftRadius, radius);
float bottomRightRadius = array.getDimension(R.styleable.LabelView_android_bottomRightRadius, radius);
// 阴影
int shadowColor = array.getColor(R.styleable.LabelView_android_shadowColor, Color.TRANSPARENT);
float shadowRadius = array.getFloat(R.styleable.LabelView_android_shadowRadius, 0.0f);
float shadowDx = array.getFloat(R.styleable.LabelView_android_shadowDx, 0.0f);
float shadowDy = array.getFloat(R.styleable.LabelView_android_shadowDy, 0.0f);
if (array.hasValue(R.styleable.LabelView_android_contentInsetLeft)) {
contentInsetLeft = array.getDimension(R.styleable.LabelView_android_contentInsetLeft, 0);
}
if (array.hasValue(R.styleable.LabelView_android_contentInsetRight)) {
contentInsetRight = array.getDimension(R.styleable.LabelView_android_contentInsetRight, 0);
}
int anInt = array.getInt(R.styleable.LabelView_fillType, 1);
// 边框
strokeWidth = array.getDimension(R.styleable.LabelView_borderWidth, 0);
try {
strokeColor = array.getColorStateList(R.styleable.LabelView_borderColor);
} catch (Exception e) {
e.printStackTrace();
}
if (strokeColor == null) {
strokeColor = ColorStateList.valueOf(array.getColor(R.styleable.LabelView_borderColor, this.defStrokeColor));
}
view.setSelected(array.getBoolean(R.styleable.LabelView_selected, false));
array.recycle();
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(getPaintStyle(anInt));
paint.setStrokeWidth(strokeWidth);
rectF = new RectF();
rect = new Rect();
path = new Path();
setRadiusPx(view, topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius);
view.setLayerType(LAYER_TYPE_HARDWARE, null);
setShadow(shadowColor, shadowRadius, shadowDx, shadowDy);
}
// 设置阴影画笔
private void setShadow(int shadowColor, float shadowRadius, float shadowDx, float shadowDy) {
if (shadowPaint == null) {
shadowPaint = new Paint();
shadowPaint.setAntiAlias(true);
shadowPaint.setStyle(Paint.Style.FILL);
}
this.shadowRadius = Math.max(shadowRadius, 0);
this.shadowDx = shadowDx;
this.shadowDy = shadowDy;
this.shadowColor = shadowColor;
shadowPaint.setColor(shadowColor);
shadowPaint.setShadowLayer(this.shadowRadius, this.shadowDx, this.shadowDy, shadowColor);
}
private Paint.Style getPaintStyle(int type) {
return switch (type) {
case 0 -> Paint.Style.FILL;
case 2 -> Paint.Style.FILL_AND_STROKE;
default -> Paint.Style.STROKE;
};
}
void setStrokeWidth(int dp) {
strokeWidth = SizeUtils.dp2px(dp);
paint.setStrokeWidth(strokeWidth);
}
void setStrokeColor(@ColorInt int boundColor) {
this.strokeColor = ColorStateList.valueOf(boundColor);
}
void setFillType(Paint.Style style) {
paint.setStyle(style);
}
void setRadiusPx(View view, float topLeft, float topRight, float bottomRight, float bottomLeft) {
radiusList[0] = topLeft;
radiusList[1] = topLeft;
radiusList[2] = topRight;
radiusList[3] = topRight;
radiusList[4] = bottomRight;
radiusList[5] = bottomRight;
radiusList[6] = bottomLeft;
radiusList[7] = bottomLeft;
hasRadius = topLeft > 0 || topRight > 0 || bottomRight > 0 || bottomLeft > 0;
float offsetPaddingL = 0;
float offsetPaddingR = 0;
float offsetPaddingT = 0;
float offsetPaddingB = 0;
boolean autoInsetLeft = contentInsetLeft == null;
boolean autoInsetRight = contentInsetRight == null;
if (hasRadius) {
if (autoInsetLeft) {
offsetPaddingL = Math.max(topLeft, bottomLeft) / 2f;
} else if (contentInsetLeft != 0) {
offsetPaddingL = contentInsetLeft;
}
if (autoInsetRight) {
offsetPaddingR = Math.max(topRight, bottomRight) / 2f;
} else if (contentInsetRight != 0) {
offsetPaddingR = contentInsetRight;
}
}
if (isDrawBorder(view)) {
offsetPaddingL = Math.max(offsetPaddingL, strokeWidth);
offsetPaddingR = Math.max(offsetPaddingL, strokeWidth);
offsetPaddingT = strokeWidth;
offsetPaddingB = strokeWidth;
}
view.setPadding((int) Math.max(view.getPaddingLeft(), offsetPaddingL),
(int) Math.max(view.getPaddingTop(), offsetPaddingT),
(int) Math.max(view.getPaddingRight(), offsetPaddingR),
(int) Math.max(view.getPaddingBottom(), offsetPaddingB));
setStroke();
view.invalidate();
}
private void setStroke() {
if (hasRadius) {
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
} else {
paint.setStrokeCap(Paint.Cap.BUTT);
paint.setStrokeJoin(Paint.Join.MITER);
}
}
void draw(Canvas canvas, View view) {
view.getDrawingRect(rect);
rectF.set(rect);
if (hasRadius) {
path.reset();
path.addRoundRect(rectF, radiusList, Path.Direction.CW);
path.close();
canvas.clipPath(path);
}
if (isDrawShadow()) {
path.reset();
rectF.left += Math.max(shadowRadius - shadowDx, 0);
rectF.right -= Math.max(shadowRadius + shadowDx, 0);
rectF.top += Math.max(shadowRadius - shadowDy, 0);
rectF.bottom -= Math.max(shadowRadius + shadowDy, 0);
path.addRoundRect(rectF, radiusList, Path.Direction.CW);
path.close();
canvas.drawPath(path, shadowPaint);
canvas.save();
canvas.clipPath(path);
}
}
void onDraw(Canvas canvas, View view) {
if (isDrawBorder(view)) {
Paint.Style style = paint.getStyle();
if (style != Paint.Style.STROKE) {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
}
paint.setColor(getBoundColor(view));
canvas.save();
if (style != Paint.Style.FILL) {
path.reset();
rectF.left += (strokeWidth / 2f - 0.5f);
rectF.top += (strokeWidth / 2f - 0.5f);
rectF.right -= strokeWidth / 2f;
rectF.bottom -= strokeWidth / 2f;
path.addRoundRect(rectF, radiusList, Path.Direction.CW);
path.close();
}
canvas.drawPath(path, paint);
canvas.restore();
}
}
Paint getPaint() {
return paint;
}
public Rect getRect() {
return rect;
}
public Path getPath() {
return path;
}
public RectF getRectF() {
return rectF;
}
public float getStrokeWidth() {
return strokeWidth;
}
public boolean isHasRadius() {
return hasRadius;
}
private int getBoundColor(View view) {
int color = this.defStrokeColor;
if (strokeColor != null) {
if (strokeColor.isStateful()) {
color = strokeColor.getColorForState(view.getDrawableState(), strokeColor.getDefaultColor());
} else {
color = strokeColor.getDefaultColor();
}
}
return color;
}
private boolean isDrawBorder(View view) {
return strokeWidth > 0 && getBoundColor(view) != Color.TRANSPARENT;
}
private boolean isDrawShadow() {
return shadowPaint != null && shadowColor != Color.TRANSPARENT && shadowRadius > 0;
}
}
自定义控件示例
public class LabelFrameLayout extends FrameLayout {
private LabelHelper helper;
public LabelFrameLayout(Context context) {
this(context, null);
}
public LabelFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LabelFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
helper = new LabelHelper(this, context, attrs);
}
public void setFillType(Paint.Style style) {
if (helper != null && helper.getPaint() != null) {
helper.setFillType(style);
invalidate();
}
}
public void setStrokeColor(@ColorInt int boundColor) {
if (helper != null) {
helper.setStrokeColor(boundColor);
invalidate();
}
}
public void setStrokeColorRes(@ColorRes int colorRes) {
if (helper != null) {
helper.setStrokeColor(getResources().getColor(colorRes));
invalidate();
}
}
public void setStrokeWidth(int dp) {
if (helper != null && helper.getPaint() != null) {
helper.setStrokeWidth(dp);
invalidate();
}
}
public void setRadius(int radiusDp) {
setRadiusPx(SizeUtils.dp2px(radiusDp));
}
public void setRadiusPx(float radius) {
setRadiusPx(radius, radius, radius, radius);
}
public void setRadius(float topLeft, float topRight, float bottomRight, float bottomLeft) {
setRadiusPx(SizeUtils.dp2px(topLeft), SizeUtils.dp2px(topRight), SizeUtils.dp2px(bottomRight), SizeUtils.dp2px(bottomLeft));
}
public void setRadiusPx(float topLeft, float topRight, float bottomRight, float bottomLeft) {
if (helper != null) {
helper.setRadiusPx(this, topLeft, topRight, bottomRight, bottomLeft);
}
}
@Override
public void invalidate() {
if (isLaidOut()) {
super.invalidate();
}
}
@Override
public void draw(Canvas canvas) {
if (helper != null) {
helper.draw(canvas, this);
}
super.draw(canvas);
}
@Override
protected void onDraw(Canvas canvas) {
if (helper != null) {
helper.onDraw(canvas, this);
}
super.onDraw(canvas);
}
}
xml使用示例
<包名.LabelFrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="100dp"
android:layout_marginBottom="10dp"
android:background="@color/subColorGray"
app:borderColor="@color/red"
app:borderWidth="1dp"
android:radius="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="阿迪斯发斯蒂芬" />
</包名.LabelFrameLayout>