【Android自定义View】绘图之实战篇(雷达图)(四)

栏目: Android · 发布时间: 5年前

内容简介:最近一直在学习先看效果图需要解决的问题

最近一直在学习 Flutter ,感觉还不错,但是 Android 也不能拉下,回顾下前3篇的内容,让我们一起画个雷达图吧。

先看效果图

【Android自定义View】绘图之实战篇(雷达图)(四)

分析

需要解决的问题

  • 正N边形的绘制
  • 虚线的绘制
  • 间隔线的绘制
  • 文字位置计算
  • 数值坐标计算

解决问题

  • 正N边形的绘制 首先,我们以屏幕中心点O ( centerX, centerY )正上方的点A为起始点,则其坐标为 centerX, centerY - radius , 则B点的坐标应该为 centerX - radius * Math.sin(∠AOB)centerY + radius - radius * Math.cos(∠AOB)
    【Android自定义View】绘图之实战篇(雷达图)(四)
/**
     * 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篇(二)

/**
     * 返回边框的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 来设置
Paint painte = new Paint();
        painte.setStrokeWidth(lineWidth);
        painte.setColor(cutlineColor);
        painte.setStyle(Paint.Style.STROKE);
        painte.setPathEffect(new DashPathEffect(new float[]{10, 10}, 0));
复制代码

绘制的话,需要将上一步计算的坐标,每个和中心点相连即可

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 < 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);
        }
复制代码
  • 文字位置计算 文字的位置计算,需要用到上一步保存的顶点坐标
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;
    }


}

复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

An Introduction to Genetic Algorithms

An Introduction to Genetic Algorithms

Melanie Mitchell / MIT Press / 1998-2-6 / USD 45.00

Genetic algorithms have been used in science and engineering as adaptive algorithms for solving practical problems and as computational models of natural evolutionary systems. This brief, accessible i......一起来看看 《An Introduction to Genetic Algorithms》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码