自定义雷达图

package com.jialai.mall.view;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.text.Layout;
 import android.text.StaticLayout;
 import android.text.TextPaint;
 import android.util.AttributeSet;
 import android.view.View;import com.jialai.mall.util.BaseUtil;
 import com.jialai.mall.util.UIUtils;import java.util.ArrayList;
 import java.util.List;import androidx.annotation.Nullable;
public class RadarView extends View {
 //数据个数
 private int count = 10;
 //网格最大半径
 private float radius;
 //中心X
 private int centerX;
 //中心Y
 private int centerY;
 //雷达区画笔
 private Paint mainPaint;
 private Paint mainPaint1;
 //文本画笔
 private TextPaint textPaint;
 //数据区画笔
 private Paint valuePaint;
 private Paint valuePaint1;//小圆点周围的小白圈
 //标题文字
 private List titles;
 //各维度分值
 private List data;
 //数据最大值
 private float maxValue = 100;
 private float angle;
 public RadarView(Context context) {
 this(context, null);
 }
 public RadarView(Context context, @Nullable AttributeSet attrs) {
 this(context, attrs, 0);
 }
 public RadarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 init();
 }
 private void init() {
 mainPaint = new Paint();
 mainPaint.setColor(0X80FF7B50);
 mainPaint.setAntiAlias(true);
 mainPaint.setStrokeWidth(1);
 mainPaint.setStyle(Paint.Style.STROKE);
 mainPaint1 = new Paint();
 mainPaint1.setColor(0X11FF7B50);
 mainPaint1.setAntiAlias(true);
 mainPaint1.setStyle(Paint.Style.FILL);
 textPaint = new TextPaint();
 textPaint.setColor(0xFFA37366);
 textPaint.setTextAlign(Paint.Align.CENTER);
 textPaint.setTextSize(UIUtils.sp2px(getContext(), 12));
 textPaint.setStrokeWidth(1);
 textPaint.setAntiAlias(true);
 valuePaint = new Paint();
 valuePaint.setColor(0XFFFF7B50);
 valuePaint.setAntiAlias(true);
 valuePaint.setStyle(Paint.Style.FILL);
 valuePaint1 = new Paint();
 valuePaint1.setColor(0XFFFFFFFF);
 valuePaint1.setAntiAlias(true);
 valuePaint1.setStrokeWidth(3);
 valuePaint1.setStyle(Paint.Style.STROKE);
 titles = new ArrayList<>(count);
 titles.add(“皱纹”);
 titles.add(“痘痘”);
 titles.add(“毛孔”);
 titles.add(“黑头”);
 titles.add(“粗糙度”);
 titles.add(“黑眼圈”);
 titles.add(“肤色”);
 titles.add(“水油”);
 titles.add(“敏感”);
 titles.add(“斑点”);
 data = new ArrayList<>();
 data.add(10.0);
 data.add(20.0);
 data.add(30.0);
 data.add(40.0);
 data.add(50.0);
 data.add(60.0);
 data.add(70.0);
 data.add(80.0);
 data.add(90.0);
 data.add(100.0);
 }
 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
 radius = Math.min(w, h) / 2 * 0.7f;
 centerX = w / 2;
 centerY = h / 2;
 //一旦size发生改变,重新绘制
 postInvalidate();
 super.onSizeChanged(w, h, oldw, oldh);
 }
 @Override
 protected void onDraw(Canvas canvas) {
 drawPolygon(canvas);
 drawLines(canvas);
 drawTitle(canvas);
 drawRegion(canvas);
 }
 /**
 * 绘制多边形
 *
 * @param canvas
/
 private void drawPolygon(Canvas canvas) {
 Path path = new Path();
 int countceng = 5;
 //1度=1PI/180 360度=2PI 那么我们每旋转一次的角度为2PI/内角个数
 //中心与相邻两个内角相连的夹角角度
 angle = (float) (2 * Math.PI / count);
 //每个蛛丝之间的间距
 float r = radius / (countceng - 1);
 for (int i = 0; i < countceng; i++) {
 //当前半径
 float curR = r * i;
 path.reset();
 Path path1 = new Path();
 for (int j = 0; j < count; j++) {
 if (j == 0) {
 path.moveTo(centerX + curR, centerY);
 if (i != countceng - 1) {
 path1.moveTo(centerX + curR, centerY);
 }
 } else {
 //对于直角三角形sin(x)是对边比斜边,cos(x)是底边比斜边,tan(x)是对边比底边
 //因此可以推导出:底边(x坐标)=斜边(半径)cos(夹角角度)
 // 对边(y坐标)=斜边(半径)sin(夹角角度)
 float x = (float) (centerX + curR * Math.cos(angle * j));
 float y = (float) (centerY + curR * Math.sin(angle * j));
 path.lineTo(x, y);
 if (i != countceng - 1) {
 path1.lineTo(x, y);
 }
 }
 }
 path.close();
 canvas.drawPath(path, mainPaint);
 if (i != countceng - 1) {
 //填充颜色
 //闭合覆盖区域
 path1.close();
 //填充覆盖区域
 mainPaint1.setAlpha(20);
 canvas.drawPath(path1, mainPaint1);
 }
 }
 }
 /
 * 绘制直线
/
 private void drawLines(Canvas canvas) {
 Path path = new Path();
 for (int i = 0; i < count; i++) {
 path.reset();
 path.moveTo(centerX, centerY);
 float x = (float) (centerX + radius * Math.cos(angle * i));
 float y = (float) (centerY + radius * Math.sin(angle * i));
 path.lineTo(x, y);
 canvas.drawPath(path, mainPaint);
 }
 }
 /*
 * 绘制标题文字
 *
 * @param canvas
/
 private void drawTitle(Canvas canvas) {
 if (count != titles.size()) {
 return;
 }
 //相关知识点:http://mikewang.blog.51cto.com/3826268/871765/
 Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
 float fontHeight = fontMetrics.descent - fontMetrics.ascent;
 //绘制文字时不让文字和雷达图形交叉,加大绘制半径
 float textRadius = radius + fontHeight;
 float pi = (float) Math.PI;
 for (int i = 0; i < count; i++) {
 float x = (float) (centerX + textRadius * Math.cos(angle * i));
 float y = (float) (centerY + textRadius * Math.sin(angle * i));
 //当前绘制标题所在顶点角度
 float degrees = angle * i;
 //从右侧开始顺时针画起
 if (degrees == 0) {//最右侧那一个
 float dis = textPaint.measureText(titles.get(i)) / (titles.get(i).length() - 1);
 // canvas.drawText(titles.get(i), x+dis, y, textPaint);
 StaticLayout.Builder sb = StaticLayout.Builder.obtain(titles.get(i), 0, titles.get(i).length(), (TextPaint) textPaint, 300)
 .setAlignment(Layout.Alignment.ALIGN_NORMAL)
 .setLineSpacing(0.0F, 1.0F)
 .setIncludePad(true);
 StaticLayout layout = sb.build();
 canvas.save();
 canvas.translate(x + (dis / 2), y - (1.5f * dis));
 layout.draw(canvas);
 canvas.restore();
 } else if (degrees > 0 && degrees < pi / 2) {//第四象限
 float dis = textPaint.measureText(titles.get(i)) / (titles.get(i).length() - 1);
 // canvas.drawText(titles.get(i), x+dis, y, textPaint);
 StaticLayout.Builder sb = StaticLayout.Builder.obtain(titles.get(i), 0, titles.get(i).length(), (TextPaint) textPaint, 300)
 .setAlignment(Layout.Alignment.ALIGN_NORMAL)
 .setLineSpacing(0.0F, 1.0F)
 .setIncludePad(true);
 StaticLayout layout = sb.build();
 canvas.save();
 canvas.translate(x + (dis / 2), y - dis);
 layout.draw(canvas);
 canvas.restore();
 } else if (degrees >= pi / 2 && degrees < pi) {//第三象限
 float dis = textPaint.measureText(titles.get(i)) / (titles.get(i).length() - 1);
 // canvas.drawText(titles.get(i), x-dis, y, textPaint);
 StaticLayout.Builder sb = StaticLayout.Builder.obtain(titles.get(i), 0, titles.get(i).length(), textPaint, 300)
 .setAlignment(Layout.Alignment.ALIGN_NORMAL)
 .setLineSpacing(0.0F, 1.0F)
 .setIncludePad(true);
 StaticLayout layout = sb.build();
 canvas.save();
 canvas.translate(x - (dis / 2), y - dis);
 layout.draw(canvas);
 canvas.restore();
 } else if (degrees == pi) {//最左侧那个
 float dis = textPaint.measureText(titles.get(i)) / (titles.get(i).length());
 // canvas.drawText(titles.get(i), x-dis, y, textPaint);
 StaticLayout.Builder sb = StaticLayout.Builder.obtain(titles.get(i), 0, titles.get(i).length(), textPaint, 300)
 .setAlignment(Layout.Alignment.ALIGN_NORMAL)
 .setLineSpacing(0.0F, 1.0F)
 .setIncludePad(true);
 StaticLayout layout = sb.build();
 canvas.save();
 canvas.translate(x - (1.5f * dis), y - (dis * 2f));
 layout.draw(canvas);
 canvas.restore();
 } else if (degrees > pi && degrees < 3 * pi / 2) {//第二象限
 float dis = textPaint.measureText(titles.get(i)) / (titles.get(i).length());
 // canvas.drawText(titles.get(i), x-dis, y, textPaint);
 StaticLayout.Builder sb = StaticLayout.Builder.obtain(titles.get(i), 0, titles.get(i).length(), textPaint, 300)
 .setAlignment(Layout.Alignment.ALIGN_NORMAL)
 .setLineSpacing(0.0F, 1.0F)
 .setIncludePad(true);
 StaticLayout layout = sb.build();
 canvas.save();
 canvas.translate(x - dis, y - (dis * 2.5f));
 layout.draw(canvas);
 canvas.restore();
 } else if (degrees >= 3 * pi / 2 && degrees <= 2 * pi) {//第一象限
 // canvas.drawText(titles.get(i), x, y, textPaint);
 float dis = textPaint.measureText(titles.get(i)) / (titles.get(i).length());
 StaticLayout.Builder sb = StaticLayout.Builder.obtain(titles.get(i), 0, titles.get(i).length(), textPaint, 300)
 .setAlignment(Layout.Alignment.ALIGN_NORMAL)
 .setLineSpacing(0.0F, 1.0F)
 .setIncludePad(true);
 StaticLayout layout = sb.build();
 canvas.save();
 canvas.translate(x + dis, y - (dis * 2.5f));
 layout.draw(canvas);
 canvas.restore();
 }
 }
 }
 /*
 * 绘制覆盖区域
/
 private void drawRegion(Canvas canvas) {
 valuePaint.setAlpha(255);
 Path path = new Path();
 for (int i = 0; i < count; i++) {
 //计算该数值与最大值比例
 Double perCenter = data.get(i) / maxValue;
 //小圆点所在位置距离圆心的距离
 double perRadius = perCenter * radius;
 float x = (float) (centerX + perRadius * Math.cos(angle * i));
 float y = (float) (centerY + perRadius * Math.sin(angle * i));
 if (i == 0) {
 path.moveTo(x, y);
 } else {
 path.lineTo(x, y);
 }
 //绘制小圆点
 canvas.drawCircle(x, y, 10, valuePaint);
 }
 //闭合覆盖区域
 path.close();
 valuePaint.setStrokeWidth(3);
 valuePaint.setStyle(Paint.Style.STROKE);
 //绘制覆盖区域外的连线
 canvas.drawPath(path, valuePaint);
 //填充覆盖区域
 valuePaint.setAlpha(128);
 valuePaint.setStyle(Paint.Style.FILL);
 canvas.drawPath(path, valuePaint);
 //绘制小红点四周的白圈
 drawPointWhite(canvas);
 }
 /*
 * 绘制小红点四周的白色
 */
 private void drawPointWhite(Canvas canvas) {
 valuePaint1.setAlpha(255);
 Path path = new Path();
 for (int i = 0; i < count; i++) {
 //计算该数值与最大值比例
 Double perCenter = data.get(i) / maxValue;
 //小圆点所在位置距离圆心的距离
 double perRadius = perCenter * radius;
 float x = (float) (centerX + perRadius * Math.cos(angle * i));
 float y = (float) (centerY + perRadius * Math.sin(angle * i));
 if (i == 0) {
 path.moveTo(x, y);
 } else {
 path.lineTo(x, y);
 }
 //绘制小圆点
 canvas.drawCircle(x, y, 12, valuePaint1);
 }
 }
 //设置数值种类
 public void setCount(int count) {
 this.count = count;
 postInvalidate();
 }
 //设置蜘蛛网颜色
 public void setMainPaint(Paint mainPaint) {
 this.mainPaint = mainPaint;
 postInvalidate();
 }
 //设置标题颜色
 public void setTextPaint(TextPaint textPaint) {
 this.textPaint = textPaint;
 }
 //设置标题
 public void setTitles(ArrayList titles) {
 this.titles = titles;
 }
 //设置覆盖局域颜色
 public void setValuePaint(Paint valuePaint) {
 this.valuePaint = valuePaint;
 postInvalidate();
 }
 //后面赋值的title是带着值的
 public void setTitles(List titles) {
 this.titles = titles;
 }
 //设置数值
 public void setData(List data) {
 this.data = data;
 postInvalidate();
 }
 public List getData() {
 return data;
 }
 //设置最大数值
 public void setMaxValue(float maxValue) {
 this.maxValue = maxValue;
 }
 }

雷达图的使用

布局文件之中

<com.jialai.mall.view.RadarView
        android:id="@+id/radarView"
        android:layout_width="match_parent"
        android:layout_height="290dp" />

代码之中使用

//评分
            //标题文字
            List titles = new ArrayList<>(10);
            titles.add(skinReportDetails.getWrinkle_score() + "\n皱纹");
            titles.add(skinReportDetails.getPockmark_score() + "\n痘痘");
            titles.add(skinReportDetails.getPore_score() + "\n毛孔");
            titles.add(skinReportDetails.getBlackhead_score() + "\n黑头");
            titles.add(skinReportDetails.getRoughness_score() + "\n粗糙度");//
            titles.add(skinReportDetails.getDark_circle_score() + "\n黑眼圈");
            titles.add(skinReportDetails.getColor_score() + "\n肤色");
            titles.add(skinReportDetails.getSkin_type_score() + "\n水油");//肤质
            titles.add(skinReportDetails.getSensitive_score() + "\n敏感");
            titles.add(skinReportDetails.getSpot_score() + "\n斑点");

            //各维度分值
            List<Double> data = new ArrayList<>();
            data.add(Double.parseDouble(skinReportDetails.getWrinkle_score()));
            data.add(Double.parseDouble(skinReportDetails.getPockmark_score()));
            data.add(Double.parseDouble(skinReportDetails.getPore_score()));
            data.add(Double.parseDouble(skinReportDetails.getBlackhead_score()));
            data.add(Double.parseDouble(skinReportDetails.getRoughness_score() ));
            data.add(Double.parseDouble(skinReportDetails.getDark_circle_score()));
            data.add(Double.parseDouble(skinReportDetails.getColor_score()));
            data.add(Double.parseDouble(skinReportDetails.getSkin_type_score()));//肤质
            data.add(Double.parseDouble(skinReportDetails.getSensitive_score()));
            data.add(Double.parseDouble(skinReportDetails.getSpot_score()));

            binding.radarView.setTitles(titles);
            binding.radarView.setData(data);

效果图

java 读取雷达文件 java生成雷达图_android