本文主要参考文章:http://blog.csdn.net/crazy__chen/article/details/50163693
当然还有:鸿洋大神的博客。
雷达图:
类似下图,能表示一个人的综合能力分布。
自定义View:
自定义属性:
新建attr.xml
包括总数 颜色 数值 文字
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="PolygonColor" format="color" />
<attr name="RegionColor" format="color" />
<attr name="TextColor" format="color" />
<attr name="count" format="integer" />
<attr name="title1" format="string" />
<attr name="title2" format="string" />
<attr name="title3" format="string" />
<attr name="title4" format="string" />
<attr name="title5" format="string" />
<attr name="title6" format="string" />
<attr name="data1" format="integer" />
<attr name="data2" format="integer" />
<attr name="data3" format="integer" />
<attr name="data4" format="integer" />
<attr name="data5" format="integer" />
<attr name="data6" format="integer" />
<declare-styleable name="RadarView">
<attr name="PolygonColor" />
<attr name="RegionColor" />
<attr name="TextColor" />
<attr name="count" />
<attr name="title1" />
<attr name="title2" />
<attr name="title3" />
<attr name="title4" />
<attr name="title5" />
<attr name="title6" />
<attr name="data1" />
<attr name="data2" />
<attr name="data3" />
<attr name="data4" />
<attr name="data5" />
<attr name="data6" />
</declare-styleable>
</resources>
新建类继承View,构造函数
这里要设置变量以及初始化变量:
private int count; //数据个数
private int PolygonColor;
private int TextColor;
private int RegionColor;
private float angle;
private float radius; //网格最大半径
private int centerX; //中心X
private int centerY; //中心Y
private List<String> titles = new ArrayList<String>();
private List<Integer> data = new ArrayList<Integer>(); //各维度分值
private float maxValue = 100; //数据最大值
private Paint mainPaint; //雷达区画笔
private Paint valuePaint; //数据区画笔
private Paint textPaint; //文本画笔
public RadarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RadarView(Context context) {
this(context, null);
}
/**
* 必要的初始化,获得一些自定义的值
*
* @param context
* @param attrs
* @param defStyle
*/
public RadarView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.RadarView, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.RadarView_count:
count = a.getInt(attr, 6);
break;
case R.styleable.RadarView_PolygonColor:
PolygonColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.RadarView_RegionColor:
RegionColor = a.getColor(attr, Color.BLUE);
break;
case R.styleable.RadarView_TextColor:
TextColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.RadarView_title1:
titles.add(a.getString(attr));
break;
case R.styleable.RadarView_title2:
titles.add(a.getString(attr));
break;
case R.styleable.RadarView_title3:
titles.add(a.getString(attr));
break;
case R.styleable.RadarView_title4:
titles.add(a.getString(attr));
break;
case R.styleable.RadarView_title5:
titles.add(a.getString(attr));
break;
case R.styleable.RadarView_title6:
titles.add(a.getString(attr));
break;
case R.styleable.RadarView_data1:
data.add(a.getInt(attr,100));
break;
case R.styleable.RadarView_data2:
data.add(a.getInt(attr,100));
break;
case R.styleable.RadarView_data3:
data.add(a.getInt(attr,100));
break;
case R.styleable.RadarView_data4:
data.add(a.getInt(attr,100));
break;
case R.styleable.RadarView_data5:
data.add(a.getInt(attr,100));
break;
case R.styleable.RadarView_data6:
data.add(a.getInt(attr,100));
break;
}
}
Log.d("data", data.toString());
a.recycle();
mainPaint = new Paint();
mainPaint.setAntiAlias(true);
mainPaint.setColor(PolygonColor);
mainPaint.setStrokeWidth(3);
mainPaint.setStyle(Paint.Style.STROKE);
valuePaint = new Paint();
valuePaint.setAntiAlias(true);
valuePaint.setColor(RegionColor);
valuePaint.setStyle(Paint.Style.FILL_AND_STROKE);
textPaint = new Paint();
textPaint.setTextSize(100);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(TextColor);
angle = (float) (Math.PI * 2 / count);
}
获取View中心点:
在onMeasure
函数之后,view
会大小变化。由之前的0长和0宽变成现在的宽高。同时会调用onSizeChanged()
函数。
//onSizeChanged方法 view的大小发生改变是被系统调用
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.d("onSizeChanged", "新宽" + w + "新高" + h + "旧宽" + oldw + "旧高" + oldh + "");
radius = Math.min(h, w) / 2 * 0.9f;
centerX = w / 2;
centerY = h / 2;
//postInvalidate();
super.onSizeChanged(w, h, oldw, oldh);
}
开始画图:
重写onDraw函数:
@Override
protected void onDraw(Canvas canvas) {
Log.d("onDraw", "onDraw");
drawPolygon(canvas);
drawLines(canvas);
drawText(canvas);
drawRegion(canvas);
}
/**
* 绘制正多边形
*/
private void drawPolygon(Canvas canvas) {
Log.d("drawPolygon", "drawPolygon");
Path path = new Path();
float r = radius / (count - 1);//r是蜘蛛丝之间的间距
for (int i = 1; i < count; i++) {//中心点不用绘制
float curR = r * i;//当前半径
path.reset();
for (int j = 0; j < count; j++) {
if (j == 0) {
path.moveTo(centerX + curR, centerY);
} else {
//根据半径,计算出蜘蛛丝上每个点的坐标
float x = (float) (centerX + curR * Math.cos(angle * j));
float y = (float) (centerY + curR * Math.sin(angle * j));
path.lineTo(x, y);
}
}
path.close();//闭合路径
canvas.drawPath(path, mainPaint);
}
}
/**
* 绘制直线
*/
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 drawText(Canvas canvas) {
Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
float fontHeight = fontMetrics.descent - fontMetrics.ascent;
for (int i = 0; i < count; i++) {
float x = (float) (centerX + (radius + fontHeight / 2) * Math.cos(angle * i));
float y = (float) (centerY + (radius + fontHeight / 2) * Math.sin(angle * i));
if (angle * i >= 0 && angle * i <= Math.PI / 2) {//第4象限
canvas.drawText(titles.get(i), x, y, textPaint);
} else if (angle * i >= 3 * Math.PI / 2 && angle * i <= Math.PI * 2) {//第3象限
canvas.drawText(titles.get(i), x, y, textPaint);
} else if (angle * i > Math.PI / 2 && angle * i <= Math.PI) {//第2象限
float dis = textPaint.measureText(titles.get(i));//文本长度
canvas.drawText(titles.get(i), x - dis, y, textPaint);
} else if (angle * i >= Math.PI && angle * i < 3 * Math.PI / 2) {//第1象限
float dis = textPaint.measureText(titles.get(i));//文本长度
canvas.drawText(titles.get(i), x - dis, y, textPaint);
}
}
}
/**
* 绘制区域
*
* @param canvas
*/
private void drawRegion(Canvas canvas) {
Path path = new Path();
valuePaint.setAlpha(255);
for (int i = 0; i < count; i++) {
double percent = data.get(i) / maxValue;
float x = (float) (centerX + radius * Math.cos(angle * i) * percent);
float y = (float) (centerY + radius * Math.sin(angle * i) * percent);
if (i == 0) {
path.moveTo(x, centerY);
} else {
path.lineTo(x, y);
}
//绘制小圆点
canvas.drawCircle(x, y, 10, valuePaint);
}
valuePaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, valuePaint);
valuePaint.setAlpha(127);
//绘制填充区域
valuePaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawPath(path, valuePaint);
}
调用测试:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:ww="http://schemas.android.com/apk/res-auto"
tools:context="com.example.administrator.radarview.MainActivity">
<com.example.administrator.radarview.RadarView
android:layout_centerInParent="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
ww:count="6"
ww:PolygonColor="@color/colorAccent"
ww:TextColor="#00ff00"
ww:RegionColor="#cc0cc0"
ww:title1="ceshi1"
ww:title2="ceshi2"
ww:title3="ceshi3"
ww:title4="ceshi4"
ww:title5="ceshi5"
ww:title6="ceshi6"
ww:data1="20"
ww:data2="30"
ww:data3="0"
ww:data4="100"
ww:data5="70"
ww:data6="50" />
</RelativeLayout>
效果: