本文地址:https://blog.csdn.net/qq_40785165/article/details/115279537,转载需附上此地址
大家好,我是小黑,一个还没秃头的程序员~~~
宝剑锋从磨砺出,梅花香自苦寒来。今天工作不努力,明天努力找工作。
今天分享的内容是前一段时间做的一个仿雷达数据分布图,效果如下:
如效果所示,控件自动分成若干等分,并在分割线处标注文字,中间分布若干数据,分割的数量以及其他样式均可自定义配置
思路如下:
代码拆解如下:
(一)继承View实现构造方法,由于篇幅原因,这个步骤就不贴代码了,具体看底下完整代码
(二)定义自定义属性并获取,定义了分割的等份、颜色、字体样式等,属性的用处都有注释,具体看代码,代码如下:
attr_radar_view.xml
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RadarChartView);
//内间距
mPadding = typedArray.getInt(R.styleable.RadarChartView_padding, DEFAULT_PADDING);
//线条的数量,决定均分成多少部分
mLineCount = typedArray.getInt(R.styleable.RadarChartView_line_count, DEFAULT_LINE_COUNT);
DEFAULT_BACKGROUND = mContext.getResources().getColor(R.color.color_grey);
DEFAULT_TEXT_COLOR = mContext.getResources().getColor(R.color.colorBlack);
//背景颜色
mBackground = typedArray.getColor(R.styleable.RadarChartView_radar_background, DEFAULT_BACKGROUND);
//文字的颜色
mTextColor = typedArray.getColor(R.styleable.RadarChartView_text_color, DEFAULT_TEXT_COLOR);
//文字的大小
mTextSize = typedArray.getColor(R.styleable.RadarChartView_text_size, DEFAULT_TEXT_SIZE);
//分割线的宽度
mLineWidth = typedArray.getColor(R.styleable.RadarChartView_line_width, DEFAULT_LINE_WIDTH);
(三)实例化各个画笔工具,代码如下:
private void initPaint() {
setBackgroundColor(Color.TRANSPARENT);
mPaintLine = new Paint();
mPaintLine.setStrokeWidth(mLineWidth);
mPaintLine.setAntiAlias(true);
mPaintLine.setStyle(Paint.Style.STROKE);
mPaintLine.setColor(Color.WHITE);
mPaintCircle = new Paint();
mPaintCircle.setStyle(Paint.Style.FILL);
mPaintCircle.setAntiAlias(true);
mPaintCircle.setColor(mBackground);
mPaintPoint = new Paint();
mPaintPoint.setColor(Color.WHITE);
mPaintPoint.setStyle(Paint.Style.FILL);
mRect = new Rect();
mPaintText = new Paint();
mPaintText.setTextSize(mTextSize);
mPaintText.setColor(mTextColor);
}
(四)测量宽高,通过canvas的旋转进行线条以及数据点的绘制,测量的宽高使用父组件的宽高即可,代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidthSize = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(mWidthSize, mWidthSize);
}
绘制时根据旋转进行分割线、数据点的绘制,比起计算坐标绘制会显得更便捷与精确,思路为:restore->rotate->save->restore->rotate->save,第一次旋转是为了绘制线条或者数据点,第二次旋转是为了将画布旋转会原来的位置,所以旋转的角度为:平均角度*索引
绘制数据的思路就是利用Bean类中的角度字段进行一定角度的旋转,旋转的起始位置为左水平线,绘制分割线以及数据的代码如下所示:
//画一个带有填充色的圆
canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2 - mPadding, mPaintCircle);
//最外层画一个有颜色的圆弧
canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2 - mPadding, mPaintLine);
canvas.save();
//根据份数求出度数
float angle = 360f / (mLineCount * 2);
//根据旋转绘制分割线
for (int i = 0; i < mLineCount; i++) {
canvas.restore();
canvas.rotate(angle * i, mWidthSize / 2, mWidthSize / 2);
canvas.drawLine(mPadding, mWidthSize / 2, mWidthSize - mPadding, mWidthSize / 2, mPaintLine);
canvas.save();
canvas.restore();
canvas.rotate(-angle * i, mWidthSize / 2, mWidthSize / 2);
canvas.save();
}
//插入标点数据,从左边的水平线开始-即180度的线开始旋转分布
for (int i = 0; i < mList.size(); i++) {
DataBean item = mList.get(i);
if (item.getStatus() == 2) {
mPaintPoint.setColor(mContext.getResources().getColor(R.color.colorAccent));
} else {
mPaintPoint.setColor(mContext.getResources().getColor(R.color.colorPrimary));
}
canvas.restore();
//根据数据传进来的度数字段进行旋转
float angle1 = item.getAngle();
Log.e(TAG, "onDraw: " + angle1 + "," + mList.size());
canvas.rotate(angle1, mWidthSize / 2, mWidthSize / 2);
canvas.drawCircle(mWidthSize / 2 - (float) Math.random() * (mWidthSize / 2 - mPadding - 80) - 50, mWidthSize / 2, 5, mPaintPoint);
canvas.save();
canvas.restore();
canvas.rotate(-angle1, mWidthSize / 2, mWidthSize / 2);
canvas.save();
}
由于文字需要保持水平角度,所以不能使用旋转,这里只能在相应位置绘制了,不过所幸位置都是有规律性的,配合Math.cos()以及Math.sin()函数就可以算出位置,如图:
代码如下:
//绘制分隔位置的文字
for (int i = 0; i < mLabels.size(); i++) {
String label = mLabels.get(i);
mPaintText.getTextBounds(label, 0, label.length(), mRect);
int radius = mWidthSize / 2 - mPadding;
float cos = (float) Math.abs(Math.cos(Math.toRadians(angle * i)));
float sin = (float) Math.abs(Math.sin(Math.toRadians(angle * i)));
float b = radius * cos;
float a = radius * sin;
float x = 0;
float y = 0;
if (angle * i == 90) {
x = mWidthSize / 2 - b - mRect.width() / 2;
y = mWidthSize / 2 - a;
} else if (angle * i > 90 && angle * i < 180) {
x = mWidthSize / 2 + b;
y = mWidthSize / 2 + mRect.height() / 2 - a;
} else if (angle * i == 180) {
x = mWidthSize / 2 + b;
y = mWidthSize / 2 + mRect.height() / 2 - a;
} else if (angle * i > 180 && angle * i < 270) {
x = mWidthSize / 2 + b;
y = mWidthSize / 2 + mRect.height() / 2 + a;
} else if (angle * i == 270) {
x = mWidthSize / 2 - b - mRect.width() / 2;
y = mWidthSize / 2 + a + mRect.height();
} else if (angle * i > 270 && angle * i < 360) {
x = mWidthSize / 2 - b - mRect.width();
y = mWidthSize / 2 + a + mRect.height() / 2;
} else {
x = mWidthSize / 2 - b - mRect.width();
y = mWidthSize / 2 + mRect.height() / 2 - a;
}
canvas.drawText(label, x, y, mPaintText);
}
canvas.save();
public class RadarChartView extends View {
private Context mContext;
private Paint mPaintLine;//绘制线的画笔
private Paint mPaintCircle;//绘制外部圆的画笔
private Paint mPaintPoint;//绘制中间点的画笔
private Rect mRect;
private Paint mPaintText;//绘制文字的画笔
public int DEFAULT_BACKGROUND;
public int DEFAULT_TEXT_SIZE = 22;
public int DEFAULT_TEXT_COLOR;
public int DEFAULT_LINE_WIDTH = 5;
public int DEFAULT_LINE_COUNT = 4;
public int DEFAULT_PADDING = 0;
private List mList = new ArrayList<>();
private int mWidthSize;
private List mLabels = new ArrayList<>();
private int mPadding;
private int mLineCount;
private int mBackground;
private int mTextSize;
private int mTextColor;
private int mLineWidth;
public RadarChartView(Context context) {
this(context, null);
}
public RadarChartView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RadarChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RadarChartView);
//内间距
mPadding = typedArray.getInt(R.styleable.RadarChartView_padding, DEFAULT_PADDING);
//线条的数量,决定均分成多少部分
mLineCount = typedArray.getInt(R.styleable.RadarChartView_line_count, DEFAULT_LINE_COUNT);
DEFAULT_BACKGROUND = mContext.getResources().getColor(R.color.color_grey);
DEFAULT_TEXT_COLOR = mContext.getResources().getColor(R.color.colorBlack);
//背景颜色
mBackground = typedArray.getColor(R.styleable.RadarChartView_radar_background, DEFAULT_BACKGROUND);
//文字的颜色
mTextColor = typedArray.getColor(R.styleable.RadarChartView_text_color, DEFAULT_TEXT_COLOR);
//文字的大小
mTextSize = typedArray.getColor(R.styleable.RadarChartView_text_size, DEFAULT_TEXT_SIZE);
//分割线的宽度
mLineWidth = typedArray.getColor(R.styleable.RadarChartView_line_width, DEFAULT_LINE_WIDTH);
initPaint();
}
public void setList(List list) {
mList = list;
}
public void setLabels(List labels) {
mLabels = labels;
}
private void initPaint() {
setBackgroundColor(Color.TRANSPARENT);
mPaintLine = new Paint();
mPaintLine.setStrokeWidth(mLineWidth);
mPaintLine.setAntiAlias(true);
mPaintLine.setStyle(Paint.Style.STROKE);
mPaintLine.setColor(Color.WHITE);
mPaintCircle = new Paint();
mPaintCircle.setStyle(Paint.Style.FILL);
mPaintCircle.setAntiAlias(true);
mPaintCircle.setColor(mBackground);
mPaintPoint = new Paint();
mPaintPoint.setColor(Color.WHITE);
mPaintPoint.setStyle(Paint.Style.FILL);
mRect = new Rect();
mPaintText = new Paint();
mPaintText.setTextSize(mTextSize);
mPaintText.setColor(mTextColor);
}
private static final String TAG = "RadarChartView";
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidthSize = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(mWidthSize, mWidthSize);
}
@Override
protected void onDraw(Canvas canvas) {
//画一个带有填充色的圆
canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2 - mPadding, mPaintCircle);
//最外层画一个有颜色的圆弧
canvas.drawCircle(mWidthSize / 2, mWidthSize / 2, mWidthSize / 2 - mPadding, mPaintLine);
canvas.save();
//根据份数求出度数
float angle = 360f / (mLineCount * 2);
//根据旋转绘制分割线
for (int i = 0; i < mLineCount; i++) {
canvas.restore();
canvas.rotate(angle * i, mWidthSize / 2, mWidthSize / 2);
canvas.drawLine(mPadding, mWidthSize / 2, mWidthSize - mPadding, mWidthSize / 2, mPaintLine);
canvas.save();
canvas.restore();
canvas.rotate(-angle * i, mWidthSize / 2, mWidthSize / 2);
canvas.save();
}
//绘制分隔位置的文字
for (int i = 0; i < mLabels.size(); i++) {
String label = mLabels.get(i);
mPaintText.getTextBounds(label, 0, label.length(), mRect);
int radius = mWidthSize / 2 - mPadding;
float cos = (float) Math.abs(Math.cos(Math.toRadians(angle * i)));
float sin = (float) Math.abs(Math.sin(Math.toRadians(angle * i)));
float b = radius * cos;
float a = radius * sin;
float x = 0;
float y = 0;
if (angle * i == 90) {
x = mWidthSize / 2 - b - mRect.width() / 2;
y = mWidthSize / 2 - a;
} else if (angle * i > 90 && angle * i < 180) {
x = mWidthSize / 2 + b;
y = mWidthSize / 2 + mRect.height() / 2 - a;
} else if (angle * i == 180) {
x = mWidthSize / 2 + b;
y = mWidthSize / 2 + mRect.height() / 2 - a;
} else if (angle * i > 180 && angle * i < 270) {
x = mWidthSize / 2 + b;
y = mWidthSize / 2 + mRect.height() / 2 + a;
} else if (angle * i == 270) {
x = mWidthSize / 2 - b - mRect.width() / 2;
y = mWidthSize / 2 + a + mRect.height();
} else if (angle * i > 270 && angle * i < 360) {
x = mWidthSize / 2 - b - mRect.width();
y = mWidthSize / 2 + a + mRect.height() / 2;
} else {
x = mWidthSize / 2 - b - mRect.width();
y = mWidthSize / 2 + mRect.height() / 2 - a;
}
canvas.drawText(label, x, y, mPaintText);
}
canvas.save();
//插入标点数据,从左边的水平线开始-即180度的线开始旋转分布
for (int i = 0; i < mList.size(); i++) {
DataBean item = mList.get(i);
if (item.getStatus() == 2) {
mPaintPoint.setColor(mContext.getResources().getColor(R.color.colorAccent));
} else {
mPaintPoint.setColor(mContext.getResources().getColor(R.color.colorPrimary));
}
canvas.restore();
//根据数据传进来的度数字段进行旋转
float angle1 = item.getAngle();
canvas.rotate(angle1, mWidthSize / 2, mWidthSize / 2);
canvas.drawCircle(mWidthSize / 2 - (float) Math.random() * (mWidthSize / 2 - mPadding - 80) - 50, mWidthSize / 2, 5, mPaintPoint);
canvas.save();
canvas.restore();
canvas.rotate(-angle1, mWidthSize / 2, mWidthSize / 2);
canvas.save();
}
super.onDraw(canvas);
}
}
实体类如下:
public class DataBean {
private String c_time;//用来计算度数的字段
private int status;//状态
private float angle;//求出的相应的度数
public DataBean(String c_time, int status) {
this.c_time = c_time;
this.status = status;
}
public float getAngle() {
//计算度数,根据自己项目的逻辑进行计算
return TimeUtils.getAngle(c_time.split(" ")[1]);
}
public String getC_time() {
return c_time;
}
public void setC_time(String c_time) {
this.c_time = c_time;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
使用的时候往控件中传入相应的文字标签以及数据即可
mRadarChartView.setLabels(Arrays.asList("00:00", "03:00", "06:00", "09:00", "12:00", "15:00", "18:00", "21:00"));
mRadarChartView.setList(mList);
这样一来,一个可以均分的数据分布图就完成了,最后,希望喜欢我文章的朋友们可以帮忙点赞、收藏、评论,也可以关注一下,如果有问题可以在评论区提出,我后面会继续写关于自己自定义控件的经验的博客,与大家分享,谢谢大家的支持与阅读!