自定义Android View组件——实现雷达图效果
2016-02-26 22:19
627 查看
今天有一个任务,在学生教育系统的客户端打开的页面实现一个雷达图,将雷达图的数据分成语文、数学、外语、文科、理科和其他。
效果图是这样
于是我就开始了思考,为了这个自定义的view组件可以在以后进行复用,所以我增加了可变的成员变量count,代表要表示的项的总数。
根据雷达图的样式,可以获得大概的绘制步骤
雷达图的形状实质是一个正n边形,确定正n边形的顶点坐标,需要确定这个正n边形的外接圆。如图所示
如图分析,设多边形为n边形,顺时针算起每个顶点的角度为 360 / n * i,外接圆的半径为r,则根据左上角坐标(0,0)可得圆心坐标为(r,r),由此可算出各个顶点的坐标。
根据分析则可以进行如下步骤的编码:
定义类RadoView.java
代码如下;
根据分析图可以发现,根据三角函数计算坐标的角度并非是360 / n,而是构造直角三角形根据三角函数计算
写一个方法计算这个角度
算出角度后,应该计算出每个角度的所在象限
注意,这个角度的所在象限是根据外接圆的圆心来判断的,以6边形为例子,每个角的度数分别是30,60,90,120….,30和60度的顶点所在的象限为第一象限,120度所在为第二象限,以此类推
编写函数int getQr(double),计算所在象限
此时根据点的象限和角度,便可以计算出顶点的x坐标和y坐标
角度在1,2象限的时候,横坐标为一个半径加上这个顶点在第二象限部分的横坐标,这个横坐标可以根据构造直角三角形,使用三角函数计算得出
同理,可以得出纵坐标的计算方法
编写函数double getPointY(double, double);
此时,我们就可以根据第i个顶点划过的角度计算出顶点的坐标,将坐标之间进行连线,就画出了正多边形
最后覆写onDraw方法,因为雷达图不止一个外接圆,所以我在类成员中定义了一个成员变量,就是radius数组,里面分别是5个外接圆的半径,分别表示雷达图中的ABCDE5个等级
并且将圆形与各个顶点之间的线条进行连接
然后根据LinkedHashMap中的变量,进行绘点,将6个点用path相连接,将绘制路径的画笔设置为stroke and fill的样式,并且将颜色设置为蓝色,透明度设置为80。
完整的onDraw方法代码如下:
附完整的运行效果和代码
.Java file: RadoView.java
效果图是这样
于是我就开始了思考,为了这个自定义的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;
}
}
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories