最近一直在学习Flutter
,感受还不错,可是Android
也不能拉下,回顾下前3篇的内容,让咱们一块儿画个雷达图吧。java
先看效果图 canvas
须要解决的问题ide
首先,咱们以屏幕中心点O ( centerX, centerY
)正上方的点A为起始点,则其坐标为centerX, centerY - radius
, 则B点的坐标应该为centerX - radius * Math.sin(∠AOB)
,centerY + radius - radius * Math.cos(∠AOB)
post
/** * arc为弧度,在顶点处创建直角坐标系,用r和arc肯定下一个点的坐标 */
public Point nextPoint(Point point, double arc, int radius) {
Point p = new Point();
p.x = (int) (point.x - radius * Math.sin(arc));
p.y = (int) (point.y + radius - radius * Math.cos(arc));
return p;
}
复制代码
请注意,此处的角度在java中要转为弧度,sideSize
为N边学习
/** * 角度制转弧度制 */
private double degree2radian() {
return 2 * Math.PI / sideSize;
}
复制代码
因此,此时,边框的Path
应该为: Path
相关内容可查看 【Android自定义View】绘图之Path篇(二)this
/** * 返回边框的path * * @param sPoint (centerX, centerY - radiu) * @param count * @param radius * @return */
private Path makePath(Point sPoint, int count, int radius) {
Path path = new Path();
path.moveTo(sPoint.x, sPoint.y);
for (int i = 1; i < count; i++) {
Point point = nextPoint(sPoint, -degree2radian() * i, radius);
path.lineTo(point.x, point.y);
}
path.close();
return path;
}
复制代码
能够使用setPathEffect
来设置spa
Paint painte = new Paint();
painte.setStrokeWidth(lineWidth);
painte.setColor(cutlineColor);
painte.setStyle(Paint.Style.STROKE);
painte.setPathEffect(new DashPathEffect(new float[]{10, 10}, 0));
复制代码
绘制的话,须要将上一步计算的坐标,每一个和中心点相连便可code
for (int i = 0; i < sideSize; i++) {
Point point = nextPoint(sPoint, -degree2radian() * i, radius);
Path path = new Path();
path.moveTo(centerX, centerY);
path.lineTo(point.x, point.y);
//绘制分割线
canvas.drawPath(path, painte);
//计算文字位置使用
pointList.add(point);
}
复制代码
间隔线的处理跟边框的绘制类似,只是传入的半径不一样orm
for (int i = 0; i < spaceCount; i++) {
int radiu = radius / spaceCount * (i + 1);
Path p = makePath(new Point(centerX, centerY - radiu), sideSize, radiu);
paint.setColor(boxlineColor);
canvas.drawPath(p, paint);
}
复制代码
文字的位置计算,须要用到上一步保存的顶点坐标cdn
for (int i = 0; i < pointList.size(); i++) {
if (labelText != null && labelText.size() > 0) {
//绘制顶点文字
drawTextTop(pointList.get(i), labelText.get(i));
}
}
复制代码
绘制文字,这里涉及到的在上一篇中有详细描述,详情可查看 【Android自定义View】绘图之文字篇(三)
/** * 绘制顶点文字 * * @param point * @param text */
private void drawTextTop(Point point, String text) {
Rect rect = new Rect();
paint.getTextBounds(text, 0, text.length(), rect);
int x;
int y;
//偏移处理
if (point.x - centerX == 0 || Math.abs(point.x - centerX) < 5) {
x = point.x;
} else if (point.x - centerX > textSpace) {
x = point.x + textSpace;
} else {
x = point.x - textSpace;
}
if (point.y - centerY == 0 || Math.abs(point.y - centerY) < 5) {
y = point.y;
} else if (point.y - centerY > textSpace) {
y = point.y + textSpace;
} else {
y = point.y - textSpace;
}
paint.setStyle(Paint.Style.FILL);
paint.setColor(textColor);
canvas.drawText(text, x, y + (rect.bottom - rect.top) / 2, paint);
paint.setStyle(Paint.Style.STROKE);
}
复制代码
数值坐标计算相对麻烦点,经过中心点正上方的坐标来推导旋转后的坐标
Path valuePath = makePath(radius, labelValue);
paint.setColor(valueColor);
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(valuePath, paint);
复制代码
private Path makePath(int radius, List<Double> values) {
Path path = new Path();
Point sPoint = new Point(centerX, (int) (centerY - radius * values.get(0)));
path.moveTo(sPoint.x, sPoint.y);
for (int i = 1; i < values.size(); i++) {
sPoint = new Point(centerX, (int) (centerY - radius * values.get(i)));
Point point = nextPoint(sPoint, -degree2radian() * i, (int) (radius * values.get(i)));
path.lineTo(point.x, point.y);
}
path.close();
return path;
}
复制代码
在加上一些自定义属性,一个雷达图就作好了
<resources>
<declare-styleable name="RadarView">
<!--线颜色-->
<attr name="ch_boxlineColor" format="color" />
<!--文字颜色-->
<attr name="ch_textColor" format="color" />
<!--分割线颜色-->
<attr name="ch_cutlineColor" format="color" />
<!--内容颜色-->
<attr name="ch_valueColor" format="color" />
<!--线宽-->
<attr name="ch_lineWidth" format="dimension" />
<!--文字大小-->
<attr name="ch_textSize" format="dimension" />
<!--几边形-->
<attr name="ch_sideSize" format="integer" />
<!--辅助线-->
<attr name="ch_spaceCount" format="integer" />
<!--文字离顶点的距离-->
<attr name="ch_textSpace" format="dimension" />
<!--边距离-->
<attr name="ch_padding" format="dimension" />
</declare-styleable>
</resources>
复制代码
完整代码以下:
public class RadarView extends View {
private Context context;
//线宽
private int lineWidth;
//线颜色
private int boxlineColor;
//内容颜色
private int valueColor;
//文字颜色
private int textColor;
//分割线颜色
private int cutlineColor;
//文字大小
private int textSize;
//文字离顶点的距离
private int textSpace;
//几边形
private int sideSize;
//辅助线
private int spaceCount;
//边距
private int padding;
//半径
private int radius;
private Paint paint;
private Canvas canvas;
//中心x
private int centerX;
//中心y
private int centerY;
private List<Point> pointList;
private List<String> labelText;
private List<Double> labelValue;
public void setLabelValue(List<Double> labelValue) {
this.labelValue = labelValue;
postInvalidate();
}
public void setLabelText(List<String> labelText) {
this.labelText = labelText;
postInvalidate();
}
public RadarView(Context context) {
super(context);
}
public RadarView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init(attrs);
}
public RadarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
init(attrs);
}
private void init(AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RadarView);
boxlineColor = array.getColor(R.styleable.RadarView_ch_boxlineColor, Color.BLACK);
textColor = array.getColor(R.styleable.RadarView_ch_textColor, Color.BLACK);
cutlineColor = array.getColor(R.styleable.RadarView_ch_cutlineColor, Color.MAGENTA);
valueColor = array.getColor(R.styleable.RadarView_ch_valueColor, Color.MAGENTA);
lineWidth = array.getDimensionPixelSize(R.styleable.RadarView_ch_lineWidth, 5);
textSize = array.getDimensionPixelSize(R.styleable.RadarView_ch_textSize, 14);
sideSize = array.getInt(R.styleable.RadarView_ch_sideSize, 6);
spaceCount = array.getInt(R.styleable.RadarView_ch_spaceCount, 4);
textSpace = array.getDimensionPixelSize(R.styleable.RadarView_ch_textSpace, 100);
padding = array.getDimensionPixelSize(R.styleable.RadarView_ch_padding, 200);
array.recycle();
paint = new Paint();
paint.setColor(boxlineColor);
paint.setStrokeWidth(lineWidth);
paint.setStyle(Paint.Style.STROKE);
paint.setTextSize(textSize);
paint.setTextAlign(Paint.Align.CENTER);
if (radius == 0) {
radius = Math.min(getScreenHeight(), getScreenWidth()) / 2 - padding;
}
pointList = new ArrayList<>();
centerX = getScreenWidth() / 2;
centerY = getScreenHeight() / 2;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.canvas = canvas;
Point sPoint = new Point(centerX, centerY - radius);
Paint painte = new Paint();
painte.setStrokeWidth(lineWidth);
painte.setColor(cutlineColor);
painte.setStyle(Paint.Style.STROKE);
painte.setPathEffect(new DashPathEffect(new float[]{10, 10}, 0));
pointList.clear();
for (int i = 0; i < sideSize; i++) {
Point point = nextPoint(sPoint, -degree2radian() * i, radius);
Path path = new Path();
path.moveTo(centerX, centerY);
path.lineTo(point.x, point.y);
//绘制分割线
canvas.drawPath(path, painte);
pointList.add(point);
}
for (int i = 0; i < pointList.size(); i++) {
if (labelText != null && labelText.size() > 0) {
//绘制顶点文字
drawTextTop(pointList.get(i), labelText.get(i));
}
}
for (int i = 0; i < spaceCount; i++) {
int radiu = radius / spaceCount * (i + 1);
Path p = makePath(new Point(centerX, centerY - radiu), sideSize, radiu);
paint.setColor(boxlineColor);
canvas.drawPath(p, paint);
}
//绘制值
if (labelValue == null || labelValue.size() == 0) {
return;
}
Path valuePath = makePath(radius, labelValue);
paint.setColor(valueColor);
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(valuePath, paint);
}
/** * 绘制顶点文字 * * @param point * @param text */
private void drawTextTop(Point point, String text) {
Rect rect = new Rect();
paint.getTextBounds(text, 0, text.length(), rect);
int x;
int y;
//偏移处理
if (point.x - centerX == 0 || Math.abs(point.x - centerX) < 5) {
x = point.x;
} else if (point.x - centerX > textSpace) {
x = point.x + textSpace;
} else {
x = point.x - textSpace;
}
if (point.y - centerY == 0 || Math.abs(point.y - centerY) < 5) {
y = point.y;
} else if (point.y - centerY > textSpace) {
y = point.y + textSpace;
} else {
y = point.y - textSpace;
}
paint.setStyle(Paint.Style.FILL);
paint.setColor(textColor);
canvas.drawText(text, x, y + (rect.bottom - rect.top) / 2, paint);
paint.setStyle(Paint.Style.STROKE);
}
/** * 返回边框的path * * @param sPoint * @param count * @param radius * @return */
private Path makePath(Point sPoint, int count, int radius) {
Path path = new Path();
path.moveTo(sPoint.x, sPoint.y);
for (int i = 1; i < count; i++) {
Point point = nextPoint(sPoint, -degree2radian() * i, radius);
path.lineTo(point.x, point.y);
}
path.close();
return path;
}
private Path makePath(int radius, List<Double> values) {
Path path = new Path();
Point sPoint = new Point(centerX, (int) (centerY - radius * values.get(0)));
path.moveTo(sPoint.x, sPoint.y);
for (int i = 1; i < values.size(); i++) {
sPoint = new Point(centerX, (int) (centerY - radius * values.get(i)));
Point point = nextPoint(sPoint, -degree2radian() * i, (int) (radius * values.get(i)));
path.lineTo(point.x, point.y);
Log.e("cheng", point.toString());
}
path.close();
return path;
}
/** * 获取屏幕宽度 * * @return */
private int getScreenWidth() {
Resources resources = getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
return dm.widthPixels;
}
/** * 获取屏幕高度 * * @return */
private int getScreenHeight() {
Resources resources = getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
return dm.heightPixels;
}
/** * 角度制转弧度制 */
private double degree2radian() {
return 2 * Math.PI / sideSize;
}
// arc为弧度,在顶点处创建直角坐标系,用r和arc肯定下一个点的坐标
public Point nextPoint(Point point, double arc) {
Point p = new Point();
p.x = (int) (point.x - radius * Math.sin(arc));
p.y = (int) (point.y + radius - radius * Math.cos(arc));
return p;
}
/** * arc为弧度,在顶点处创建直角坐标系,用r和arc肯定下一个点的坐标 */
public Point nextPoint(Point point, double arc, int radius) {
Point p = new Point();
p.x = (int) (point.x - radius * Math.sin(arc));
p.y = (int) (point.y + radius - radius * Math.cos(arc));
return p;
}
}
复制代码