您的位置:首页 > 移动开发 > Android开发

自定义Android View组件——实现雷达图效果

2016-02-26 22:19 627 查看
今天有一个任务,在学生教育系统的客户端打开的页面实现一个雷达图,将雷达图的数据分成语文、数学、外语、文科、理科和其他。

效果图是这样



于是我就开始了思考,为了这个自定义的view组件可以在以后进行复用,所以我增加了可变的成员变量count,代表要表示的项的总数。

根据雷达图的样式,可以获得大概的绘制步骤

雷达图的形状实质是一个正n边形,确定正n边形的顶点坐标,需要确定这个正n边形的外接圆。如图所示



如图分析,设多边形为n边形,顺时针算起每个顶点的角度为 360 / n * i,外接圆的半径为r,则根据左上角坐标(0,0)可得圆心坐标为(r,r),由此可算出各个顶点的坐标。

根据分析则可以进行如下步骤的编码:

定义类RadoView.java

代码如下;

public class RadoView extends View {

private int count = 6;   //多边形的变数

private int[] radius = new int[]{100, 200, 300, 400, 500};  //各个外接圆的变数

private int maxRadius = radius[radius.length - 1];

private int[] marks = new int[count];
private String[] keys = new String[count];

LinkedHashMap<String, Integer> map = new LinkedHashMap<>();

private Paint paintLine;   //画线

private Paint paintMarkLine;  //分值连线
private Paint paintMarkPoint;  //分值画点

private Paint paintText;  //绘制文字

private double x;   //当前点的横坐标
private double y;    //当前点的纵坐标
private double lastX;  //上一次的坐标
private double lastY;

public RadoView(Context context) {
super(context);
}

public RadoView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public RadoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}


根据分析图可以发现,根据三角函数计算坐标的角度并非是360 / n,而是构造直角三角形根据三角函数计算

写一个方法计算这个角度

/**
* 得到需要计算的角度
*
* @param angle 角度,例:30.60.90
* @return res
*/
private double getNewAngle(double angle) {
double res = angle;
if (angle >= 0 && angle <= 90) {
res = 90 - angle;
} else if (angle > 90 && angle <= 180) {
res = angle - 90;
} else if (angle > 180 && angle <= 270) {
res = 270 - angle;
} else if (angle > 270 && angle <= 360) {
res = angle - 270;
}
return res;
}


算出角度后,应该计算出每个角度的所在象限

注意,这个角度的所在象限是根据外接圆的圆心来判断的,以6边形为例子,每个角的度数分别是30,60,90,120….,30和60度的顶点所在的象限为第一象限,120度所在为第二象限,以此类推

编写函数int getQr(double),计算所在象限

private int getQr(double angle) {
int res = 0;
if (angle >= 0 && angle <= 90) {
res = 1;
} else if (angle > 90 && angle <= 180) {
res = 2;
} else if (angle > 180 && angle <= 270) {
res = 3;
} else if (angle > 270 && angle <= 360) {
res = 4;
}
return res;
}


此时根据点的象限和角度,便可以计算出顶点的x坐标和y坐标

角度在1,2象限的时候,横坐标为一个半径加上这个顶点在第二象限部分的横坐标,这个横坐标可以根据构造直角三角形,使用三角函数计算得出

private double getPointX(double angle, double radius) {
double newAngle = getNewAngle(angle);
double res = 0;
double width = radius * Math.cos(newAngle / 180 * Math.PI);
int qr = getQr(angle);
switch (qr) {
case 1:
case 2:
res = maxRadius + width;
break;
case 3:
case 4:
res = maxRadius - width;
break;
default:
break;
}
return res;
}


同理,可以得出纵坐标的计算方法

编写函数double getPointY(double, double);

private double getPointY(double angle, double radius) {
double newAngle = getNewAngle(angle);
double height = radius * Math.sin(newAngle / 180 * Math.PI);
double res = 0;
int qr = getQr(angle);
switch (qr) {
case 1:
case 4:
res = maxRadius - height;
break;
case 2:
case 3:
res = maxRadius + height;
break;
default:
break;
}
return res;
}


此时,我们就可以根据第i个顶点划过的角度计算出顶点的坐标,将坐标之间进行连线,就画出了正多边形

/**
* 绘制多边形,radius为其外接圆半径
*
* @param canvas
* @param littleAngle
* @param radius
*/
private void drawStroke(Canvas canvas, double littleAngle, double radius) {
for (int i = 0; i < count; i++) {
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
x = getPointX(littleAngle * i, radius);  //当前顶点的坐标
y = getPointY(littleAngle * i, radius);
canvas.drawPoint((float) x, (float) y, paint);
canvas.drawLine((float) maxRadius, (float) maxRadius, (float) x, (float) y, paintLine);
if (i > 0) {
canvas.drawLine((float) lastX, (float) lastY, (float) x, (float) y, paintLine);
}
if (i == (count - 1)) {   //如果是最后一个顶点,则将其与第一个顶点进行连线
canvas.drawLine((float) x, (float) y, (float) getPointX(0, radius), (float) getPointY(0, radius), paintLine);
}
lastX = x;
lastY = y;  //记录上个顶点的坐标,方便进行连线
}


最后覆写onDraw方法,因为雷达图不止一个外接圆,所以我在类成员中定义了一个成员变量,就是radius数组,里面分别是5个外接圆的半径,分别表示雷达图中的ABCDE5个等级

map.put("语文", 200);
map.put("数学", 300);
map.put("外语", 320);
map.put("文科", 450);
map.put("理科", 270);
map.put("其他", 320);

Iterator iterator = map.entrySet().iterator();
int j = 0;
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
String key = (String) entry.getKey();
Integer value = (Integer) entry.getValue();
marks[j] = value;
keys[j] = key;
j++;
}


并且将圆形与各个顶点之间的线条进行连接

然后根据LinkedHashMap中的变量,进行绘点,将6个点用path相连接,将绘制路径的画笔设置为stroke and fill的样式,并且将颜色设置为蓝色,透明度设置为80。

paintLine = new Paint();
paintLine.setColor(Color.BLACK);
paintLine.setStyle(Paint.Style.STROKE);
paintLine.setStrokeWidth(3);

paintText = new Paint();
paintText.setColor(Color.BLACK);
paintText.setTextSize(25);

for (int i = 0; i < radius.length; i++) {
drawStroke(canvas, littleAngle, radius[i]);
}

//分数点
paintMarkPoint = new Paint();
paintMarkPoint.setColor(Color.parseColor("#3F51B5"));
paintMarkPoint.setStyle(Paint.Style.FILL);

//评分点的连线
paintMarkLine = new Paint();
paintMarkLine.setAntiAlias(true);
paintMarkLine.setColor(Color.parseColor("#3F51B5"));
paintMarkLine.setStyle(Paint.Style.FILL_AND_STROKE);
paintMarkLine.setAlpha(80);

Path path = new Path();
path.reset();
for (int i = 0; i < marks.length; i++) {
x = getPointX(littleAngle * i, marks[i]);
y = getPointY(littleAngle * i, marks[i]);
canvas.drawCircle((float) x, (float) y, 10, paintMarkPoint);
if (i == 0) {
path.moveTo((float) x, (float) y);
} else {
path.lineTo((float) x, (float) y);
}
lastX = x;
lastY = y;
}
canvas.drawPath(path, paintMarkLine);
}


完整的onDraw方法代码如下:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

double littleAngle = 360 / count;

map.put("语文", 200); map.put("数学", 300); map.put("外语", 320); map.put("文科", 450); map.put("理科", 270); map.put("其他", 320); Iterator iterator = map.entrySet().iterator(); int j = 0; while (iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); String key = (String) entry.getKey(); Integer value = (Integer) entry.getValue(); marks[j] = value; keys[j] = key; j++; }

paintLine = new Paint(); paintLine.setColor(Color.BLACK); paintLine.setStyle(Paint.Style.STROKE); paintLine.setStrokeWidth(3); paintText = new Paint(); paintText.setColor(Color.BLACK); paintText.setTextSize(25); for (int i = 0; i < radius.length; i++) { drawStroke(canvas, littleAngle, radius[i]); } //分数点 paintMarkPoint = new Paint(); paintMarkPoint.setColor(Color.parseColor("#3F51B5")); paintMarkPoint.setStyle(Paint.Style.FILL); //评分点的连线 paintMarkLine = new Paint(); paintMarkLine.setAntiAlias(true); paintMarkLine.setColor(Color.parseColor("#3F51B5")); paintMarkLine.setStyle(Paint.Style.FILL_AND_STROKE); paintMarkLine.setAlpha(80); Path path = new Path(); path.reset(); for (int i = 0; i < marks.length; i++) { x = getPointX(littleAngle * i, marks[i]); y = getPointY(littleAngle * i, marks[i]); canvas.drawCircle((float) x, (float) y, 10, paintMarkPoint); if (i == 0) { path.moveTo((float) x, (float) y); } else { path.lineTo((float) x, (float) y); } lastX = x; lastY = y; } canvas.drawPath(path, paintMarkLine); }


附完整的运行效果和代码

.Java file: RadoView.java



package com.example.administrator.myapplication;

import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* Created by Administrator on 2016/2/26.
* 自定义雷达图控件
*/
public class RadoView extends View {

private int count = 6;

private int[] radius = new int[]{100, 200, 300, 400, 500};

private int maxRadius = radius[radius.length - 1];

private int[] marks = new int[count];
private String[] keys = new String[count];

LinkedHashMap<String, Integer> map = new LinkedHashMap<>();

private Paint paintLine;

private Paint paintMarkLine;
private Paint paintMarkPoint;

private Paint paintText;

private double x;
private double y;
private double lastX;
private double lastY;

public RadoView(Context context) {
super(context);
}

public RadoView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public RadoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

double littleAngle = 360 / count;

map.put("语文", 200); map.put("数学", 300); map.put("外语", 320); map.put("文科", 450); map.put("理科", 270); map.put("其他", 320); Iterator iterator = map.entrySet().iterator(); int j = 0; while (iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); String key = (String) entry.getKey(); Integer value = (Integer) entry.getValue(); marks[j] = value; keys[j] = key; j++; }

paintLine = new Paint(); paintLine.setColor(Color.BLACK); paintLine.setStyle(Paint.Style.STROKE); paintLine.setStrokeWidth(3); paintText = new Paint(); paintText.setColor(Color.BLACK); paintText.setTextSize(25); for (int i = 0; i < radius.length; i++) { drawStroke(canvas, littleAngle, radius[i]); } //分数点 paintMarkPoint = new Paint(); paintMarkPoint.setColor(Color.parseColor("#3F51B5")); paintMarkPoint.setStyle(Paint.Style.FILL); //评分点的连线 paintMarkLine = new Paint(); paintMarkLine.setAntiAlias(true); paintMarkLine.setColor(Color.parseColor("#3F51B5")); paintMarkLine.setStyle(Paint.Style.FILL_AND_STROKE); paintMarkLine.setAlpha(80); Path path = new Path(); path.reset(); for (int i = 0; i < marks.length; i++) { x = getPointX(littleAngle * i, marks[i]); y = getPointY(littleAngle * i, marks[i]); canvas.drawCircle((float) x, (float) y, 10, paintMarkPoint); if (i == 0) { path.moveTo((float) x, (float) y); } else { path.lineTo((float) x, (float) y); } lastX = x; lastY = y; } canvas.drawPath(path, paintMarkLine); }

/**
* 绘制多边形,radius为其外接圆半径
*
* @param canvas
* @param littleAngle
* @param radius
*/
private void drawStroke(Canvas canvas, double littleAngle, double radius) {
for (int i = 0; i < count; i++) {
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
x = getPointX(littleAngle * i, radius);
y = getPointY(littleAngle * i, radius);
canvas.drawPoint((float) x, (float) y, paint);
canvas.drawLine((float) maxRadius, (float) maxRadius, (float) x, (float) y, paintLine);
if (i > 0) {
canvas.drawLine((float) lastX, (float) lastY, (float) x, (float) y, paintLine);
}
if (i == (count - 1)) {
canvas.drawLine((float) x, (float) y, (float) getPointX(0, radius), (float) getPointY(0, radius), paintLine);
}
lastX = x;
lastY = y;
if (radius == maxRadius) {
//如果是最外层的园,则加上文字
canvas.drawText(keys[i], (float) (lastX - 20), (float) (lastY + 20), paintText);
}
}
}

/** * 得到需要计算的角度 * * @param angle 角度,例:30.60.90 * @return res */ private double getNewAngle(double angle) { double res = angle; if (angle >= 0 && angle <= 90) { res = 90 - angle; } else if (angle > 90 && angle <= 180) { res = angle - 90; } else if (angle > 180 && angle <= 270) { res = 270 - angle; } else if (angle > 270 && angle <= 360) { res = angle - 270; } return res; }

/**
* 若以圆心为原点,返回该角度顶点的所在象限
*
* @param angle
* @return
*/
private int getQr(double angle) { int res = 0; if (angle >= 0 && angle <= 90) { res = 1; } else if (angle > 90 && angle <= 180) { res = 2; } else if (angle > 180 && angle <= 270) { res = 3; } else if (angle > 270 && angle <= 360) { res = 4; } return res; }

/**
* 返回多边形顶点X坐标
*
* @param angle
* @return
*/
private double getPointX(double angle, double radius) { double newAngle = getNewAngle(angle); double res = 0; double width = radius * Math.cos(newAngle / 180 * Math.PI); int qr = getQr(angle); switch (qr) { case 1: case 2: res = maxRadius + width; break; case 3: case 4: res = maxRadius - width; break; default: break; } return res; }

/**
* 返回多边形顶点Y坐标
*/
private double getPointY(double angle, double radius) { double newAngle = getNewAngle(angle); double height = radius * Math.sin(newAngle / 180 * Math.PI); double res = 0; int qr = getQr(angle); switch (qr) { case 1: case 4: res = maxRadius - height; break; case 2: case 3: res = maxRadius + height; break; default: break; } return res; }

public void setMap(LinkedHashMap<String, Integer> map) {
this.map = map;
}

public LinkedHashMap<String, Integer> getMap() {
return map;
}

public int getCount() {
return count;
}

public void setCount(int count) {
this.count = count;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android