简介
自定义drawable,带箭头对话框背景,三角形+矩形组合。应用于对话框背景、提示语背景等。
可设置箭头显示方向、箭头大小、箭头导圆角尺寸、矩形尺寸、矩形导圆角尺寸、背景颜色、drawable padding值(影响宿主控件padding)。
希望能给大家带来方便。
一、效果图
示例代码:
ArrowsDrawable drawable = new ArrowsDrawable(ArrowsDrawable.RIGHT, ConvertUtils.dp2px(8));
drawable.setArrowsRadius(ConvertUtils.dp2px(3));
drawable.setArrowsHeight(ConvertUtils.dp2px(8));
drawable.setArrowsWidth(ConvertUtils.dp2px(12));
drawable.setPadding(ConvertUtils.dp2px(10));
drawable.setColor(Color.DKGRAY);
textView.setBackground(drawable);
二、API详解
构造方法
//箭头方向,箭头高度
public ArrowsDrawable(@Position int arrowsPosition, float arrowsHeight)
设置drawable padding
drawable设置padding之后,会影响使用该drawable控件的padding。例如示例中drawable设置了padding,宿主控件是TextView, 即该TextView也会设置了相同的padding值,这是TextView的特性。另外,该api将箭头高度加到对应的padding值中了,比如箭头方向朝上,drawable设置4个padding均为10dp, 那么drawable paddingTop的值 = 10dp + 箭头高度值,其余均是10dp。
public void setPadding(int p)
public void setPadding(int left, int top, int right, int bottom)
设置箭头方向
LEFT, RIGHT, TOP, BOTTOM
备注:设置箭头方向,如果该drawable的宿主控件已经显示,需要动态设置箭头方向,需要宿主控件再次设置backgroup或src,否则drawable的padding不会生效。
public void setArrowsPosition(@Position int arrowsPosition)
public void setArrowsPosition(@Position int arrowsPosition, View hostView)
设置箭头距离矩形边(相对于矩形上边或左边)的距离, 默认10dp
public void setArrowsPadding(float arrowsPadding)
设置箭头圆角
设置箭头突出角的圆角尺寸,给该角导圆角,默认2dp
public void setArrowsRadius(float arrowsRadius)
其它API
//设置箭头高度
drawable.setArrowsHeight(ConvertUtils.dp2px(8));
//设置箭头宽度,默认是箭头高度的2倍
drawable.setArrowsWidth(ConvertUtils.dp2px(12));
//设置背景色
drawable.setColor(Color.DKGRAY);
扩展
如果 drawable需要设置margin效果, 可以通过InsetDrawable实现。
InsetDrawable insetDrawable = new InsetDrawable(arrowsDrawable, padding);
tv.setBackground(insetDrawable);
三、源码
package com.ttkx.deviceinfo.bkchart;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.GradientDrawable;
import android.view.Gravity;
import android.view.View;
import android.widget.ImageView;
import com.blankj.utilcode.util.ConvertUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import androidx.annotation.IntDef;
/**
* 带箭头的Drawable
* <p>
* Created by liuyu
* on 2021/2/4
*/
public class ArrowsDrawable extends GradientDrawable {
//箭头显示位置
public static final int LEFT = Gravity.LEFT;
public static final int TOP = Gravity.TOP;
public static final int RIGHT = Gravity.RIGHT;
public static final int BOTTOM = Gravity.BOTTOM;
private int mLeft;
private int mTop;
private int mRight;
private int mBottom;
private Rect mPadding;
@IntDef({LEFT, RIGHT, TOP, BOTTOM})
@Retention(RetentionPolicy.SOURCE)
public @interface Position {
}
private Paint mPaint;
private int arrowsPosition = BOTTOM;
private float arrowsRadius = dp2px(2);
private float arrowsHeight = dp2px(5);
private float arrowsWidth;
private float mRadius = dp2px(8);
private float arrowsPadding = mRadius + dp2px(10);//箭头padding
private Path mPath;
public ArrowsDrawable(@Position int arrowsPosition, float arrowsHeight) {
this.arrowsPosition = arrowsPosition;
this.arrowsHeight = arrowsHeight;
initPaint();
}
@Override
public boolean getPadding(Rect padding) {
if (mPadding != null) {
padding.set(mPadding);
return true;
} else {
return super.getPadding(padding);
}
}
public void setPadding(int p) {
setPadding(p, p, p, p);
}
/**
* 设置drawable padding
*
* @param left
* @param top
* @param right
* @param bottom
*/
public void setPadding(int left, int top, int right, int bottom) {
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
if (mPadding == null) {
mPadding = new Rect();
}
switch (arrowsPosition) {
case LEFT:
left += arrowsHeight;
break;
case TOP:
top += arrowsHeight;
break;
case RIGHT:
right += arrowsHeight;
break;
default:
bottom += arrowsHeight;
break;
}
mPadding.set(left, top, right, bottom);
invalidateSelf();
//super.setPadding(left, top, right, bottom);api=29,10.0
}
/**
* 设置箭头方向
* <p>
* LEFT, RIGHT, TOP, BOTTOM
*
* @param arrowsPosition 箭头方向
* @param hostView 宿主view (设置宿主view,是为了当箭头方向发生改变时,drawable的padding值也会改变,hostView重新设置drawable,让其执行getPadding(Rect rect)方法,让自定义的padding生效)
*/
public void setArrowsPosition(@Position int arrowsPosition, View hostView) {
boolean update = arrowsPosition != this.arrowsPosition;
setArrowsPosition(arrowsPosition);
if (update && hostView != null) {
if (hostView instanceof ImageView && ((ImageView) hostView).getDrawable() != null) {
((ImageView) hostView).setImageDrawable(this);
} else {
hostView.setBackground(this);
}
}
}
public void setArrowsPosition(@Position int arrowsPosition) {
this.arrowsPosition = arrowsPosition;
setPadding(mLeft, mTop, mRight, mBottom);
invalidateSelf();
}
/**
* 设置箭头圆角
*
* @param arrowsRadius
*/
public void setArrowsRadius(float arrowsRadius) {
this.arrowsRadius = arrowsRadius;
invalidateSelf();
}
/**
* 设置箭头高度
*
* @param arrowsHeight
*/
public void setArrowsHeight(float arrowsHeight) {
this.arrowsHeight = arrowsHeight;
invalidateSelf();
}
/**
* 设置箭头宽度
*
* @param arrowsWidth
*/
public void setArrowsWidth(float arrowsWidth) {
this.arrowsWidth = arrowsWidth;
}
/**
* 设置箭头离矩形边的距离(参照:矩形的左边或上边)
*
* @param arrowsPadding
*/
public void setArrowsPadding(float arrowsPadding) {
this.arrowsPadding = arrowsPadding;
invalidateSelf();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(1);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPath = new Path();
}
@Override
protected void onBoundsChange(Rect r) {
super.onBoundsChange(r);
int width = r.width();
int height = r.height();
float aw = getArrowsWidth();
float ar = aw / 2;
//三个点的坐标和矩形
float px1 = r.left + arrowsPadding;
float py1 = r.top + height - this.arrowsHeight;
float px2 = px1 + aw;
float py2 = py1;
float px3 = px1 + ar;
float py3 = py1 + this.arrowsHeight;
//矩形起始点坐标
float sx = r.left;
float sy = r.top;
//矩形宽高度
float rectWidth = width;
float rectHeight = height - this.arrowsHeight;
switch (arrowsPosition) {
case LEFT:
px1 = r.left + this.arrowsHeight;
py1 = r.top + arrowsPadding;
px2 = px1;
py2 = py1 + aw;
px3 = px1 - this.arrowsHeight;
py3 = py1 + ar;
sx = px1;
sy = r.top;
rectWidth = width - this.arrowsHeight;
rectHeight = height;
break;
case TOP:
px1 = r.left + arrowsPadding;
py1 = r.top + this.arrowsHeight;
px2 = px1 + aw;
py2 = py1;
px3 = px1 + ar;
py3 = py1 - this.arrowsHeight;
sx = r.left;
sy = r.top + this.arrowsHeight;
rectWidth = width;
rectHeight = height - this.arrowsHeight;
break;
case RIGHT:
px1 = r.left + width - this.arrowsHeight;
py1 = r.top + arrowsPadding;
px2 = px1;
py2 = py1 + aw;
px3 = px1 + this.arrowsHeight;
py3 = py1 + ar;
sx = r.left;
sy = r.top;
rectWidth = width - this.arrowsHeight;
rectHeight = height;
break;
}
mPath.reset();
addTrianglePath(mPath, arrowsRadius, px1, py1, px2, py2, px3, py3);
RectF rectF = new RectF(sx, sy, sx + rectWidth, sy + rectHeight);
mPath.addRoundRect(rectF, mRadius, mRadius, Path.Direction.CW);
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
canvas.drawPath(mPath, mPaint);
}
/**
* 添加三角形path
*
* @param path 路径
* @param radiu 三角形突出角圆角半径
* @param x1 与内容框连接1点X坐标 (起始点)
* @param y1 与内容框连接1点y坐标
* @param x2 与内容框连接2点X坐标
* @param y2 与内容框连接2点Xy坐标
* @param x3 三角形突出角x坐标
* @param y3 三角形突出角y坐标
*/
private void addTrianglePath(Path path, float radiu, float x1, float y1, float x2, float y2, float x3, float y3) {
//起始点
path.moveTo(x1, y1);
path.lineTo(x2, y2);
float[] ps2 = getCornerCoordinateEnd(radiu, x2, y2, x3, y3);
path.lineTo(ps2[0], ps2[1]);
float[] ps3 = getCornerCoordinateStart(radiu, x3, y3, x1, y1);
//绘制圆角
path.cubicTo(ps2[0], ps2[1], x3, y3, ps3[0], ps3[1]);
//关闭三角形
path.close();
}
/**
* 获取圆角坐标(圆角半径长度相对于起始点x1,y1)
*
* @param radiu 圆角半径
* @param x1 起始点x坐标
* @param y1 起始点y坐标
* @param x2 结束点x坐标
* @param y2 结束点y坐标
* @return
*/
private float[] getCornerCoordinateStart(float radiu, float x1, float y1, float x2, float y2) {
double degree = MathHelper.getDegree(x1, y1, x2, y2);
double lx = MathHelper.getRightSideFromDegree(degree, radiu);
double ly = MathHelper.getLeftSideFromDegree(degree, radiu);
double v2 = x1 + lx;
double v3 = y1 + ly;
return new float[]{(float) v2, (float) v3};
}
/**
* 获取圆角坐标(圆角半径长度相对于结束点x2,y2)
*
* @param radiu 圆角半径
* @param x1 起始点x坐标
* @param y1 起始点y坐标
* @param x2 结束点x坐标
* @param y2 结束点y坐标
* @return
*/
private float[] getCornerCoordinateEnd(float radiu, float x1, float y1, float x2, float y2) {
double degree = MathHelper.getDegree(x1, y1, x2, y2);
double lx = MathHelper.getRightSideFromDegree(degree, radiu);
double ly = MathHelper.getLeftSideFromDegree(degree, radiu);
double v2 = x2 - lx;
double v3 = y2 - ly;
return new float[]{(float) v2, (float) v3};
}
@Override
public void setColor(int color) {
if (mPaint != null) {
mPaint.setColor(color);
}
invalidateSelf();
}
/**
* 设置矩形的圆角
*
* @param radius
*/
@Override
public void setCornerRadius(float radius) {
mRadius = radius;
}
public float getArrowsHeight() {
return arrowsHeight;
}
public int getArrowsPosition() {
return arrowsPosition;
}
public float getArrowsRadius() {
return arrowsRadius;
}
public float getArrowsWidth() {
return arrowsWidth <= 0 ? getArrowsHeight() * 2 : arrowsWidth;
}
public float getRadius() {
return mRadius;
}
public float getArrowsPadding() {
return arrowsPadding;
}
public float getArrowsCenterDistance() {
return (int) (getArrowsPadding() + getArrowsWidth() / 2);
}
private static int dp2px(final float dpValue) {
final float scale = Resources.getSystem().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
public class MathHelper {
private static MathHelper instance = null;
//Position位置
private float mPosX = 0.0f;
private float mPosY = 0.0f;
private PointF mPointF = new PointF();
//除法运算精度
private static final int DEFAULT_DIV_SCALE = 10;
private MathHelper() {
}
public static synchronized MathHelper getInstance() {
if (instance == null) {
instance = new MathHelper();
}
return instance;
}
//两点间的角度
public static double getDegree(float sx, float sy, float tx, float ty) {
float nX = tx - sx;
float nY = ty - sy;
double angrad = 0d, angel = 0d, tpi = 0d;
float tan = 0.0f;
if (Float.compare(nX, 0.0f) != 0) {
tan = Math.abs(nY / nX);
angel = Math.atan(tan);
if (Float.compare(nX, 0.0f) == 1) {
if (Float.compare(nY, 0.0f) == 1 || Float.compare(nY, 0.0f) == 0) {
angrad = angel;
} else {
angrad = 2 * Math.PI - angel;
}
} else {
if (Float.compare(nY, 0.0f) == 1 || Float.compare(nY, 0.0f) == 0) {
angrad = Math.PI - angel;
} else {
angrad = Math.PI + angel;
}
}
} else {
tpi = Math.PI / 2;
if (Float.compare(nY, 0.0f) == 1) {
angrad = tpi;
} else {
angrad = -1 * tpi;
}
}
return Math.toDegrees(angrad);
}
/**
* 直角三角形 根据角度和斜边求直角边
*
* @param degree 角度
* @param width 斜边
* @return 直角边长
*/
public static double getRightSideFromDegree(double degree, double width) {
double cos = Math.cos(Math.toRadians(degree));
return width * cos;
}
public static double getLeftSideFromDegree(double degree, double width) {
double sin = Math.sin(Math.toRadians(degree));
return width * sin;
}
}