Android Path 最佳實踐之繪制雷達圖
第一步:繪制蜘蛛網絡
private void init() {
mainPaint=new Paint();
mainPaint.setColor(Color.BLACK);
mainPaint.setAntiAlias(true);
mainPaint.setStrokeWidth(1);
mainPaint.setStyle(Paint.Style.STROKE);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
radius=Math.min(w,h)/2*0.9f;
centerX=w/2;
centerY=h/2;
//一旦size發生改變,重新繪制
postInvalidate();
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onDraw(Canvas canvas) {
drawPolygon(canvas);
}
/**
- 繪制多邊形
- @param canvas
/
private void drawPolygon(Canvas canvas){
Path path=new Path();
//1度=1PI/180 360度=2PI 那么我們每旋轉一次的角度為2PI/內角個數
//中心與相鄰兩個內角相連的夾角角度
angle= (float) (2*Math.PI/count);
//每個蛛絲之間的間距
float r= radius/(count-1);
for (int i = 0; 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 {
//對于直角三角形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);
}
}
path.close();
canvas.drawPath(path,mainPaint);
}</code></pre>
繪制蜘蛛網絡其實就是繪制指定邊數的正多邊形,這一步比較簡單,比較難的可能就是每個頂點的算法,相關注釋我都寫了,還有一張來自互聯網的圖以助于思考,如下:

多邊形夾角示意圖
繪制出的多邊形成品如下:

多邊形效果.gif
動畫效果只是寫了 set 方法,用 handler 實現,代碼如下:
//設置數值種類
public void setCount(int count) {
this.count = count;
postInvalidate();
}
//設置蜘蛛網顏色
public void setMainPaint(Paint mainPaint) {
this.mainPaint = mainPaint;
postInvalidate();
}</code></pre>
調用方法:
mainPaint=new Paint();
mainPaint.setAntiAlias(true);
mainPaint.setStrokeWidth(1);
mainPaint.setStyle(Paint.Style.STROKE);
Handler handler=new Handler();
for (int i = 3; i < 20; i++) {
final int finalI = i;
handler.postDelayed(new Runnable() {
@Override
public void run() {
mRdv.setCount(finalI);
mainPaint.setStrokeWidth(finalI);
mRdv.setMainPaint(mainPaint);
}
},i*300);
}
第二步:繪制對角線
/**
- 繪制直線
*/
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);
}
}</code></pre>
這一步比較簡單,就是將中心點和各個頂點連接起來,效果如下:

多邊形效果.gif
第三步:繪制標題文字
/**
- 繪制標題文字
*
@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;
double pi = 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 && degrees < pi / 2) {//第四象限
float dis=textPaint.measureText(titles.get(i))/(titles.get(i).length()-1);
canvas.drawText(titles.get(i), x+dis, y, textPaint);
} 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);
} 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);
} else if (degrees >= 3 * pi / 2 && degrees <= 2 * pi) {//第一象限
canvas.drawText(titles.get(i), x, y, textPaint);
}
}
}</code></pre>
效果如下:

image.png
第四步:繪制覆蓋區域
要繪制覆蓋區域,首先要指定最大值和每個分類的具體數值,有了這些數值之后,就可以繪制了。
代碼如下:
/**
- 繪制覆蓋區域
*/
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.setStyle(Paint.Style.STROKE);
//繪制覆蓋區域外的連線
canvas.drawPath(path, valuePaint);
//填充覆蓋區域
valuePaint.setAlpha(128);
valuePaint.setStyle(Paint.Style.FILL);
canvas.drawPath(path,valuePaint);
}</code></pre> 看一下效果:

image.png
再來看一下動態的效果吧:

多邊形效果.gif
總結
終于完成了,全部代碼在下面:
主要是參考 crazy__chen 大神的博客,鏈接貼在下面,做了一遍其實還蠻簡單的,這個控件還有很多不完善的,如果實際使用需要改善的地方還有很多,如果有不足希望大家可以告訴我,謝謝!!
參考資料
來自:http://www.jianshu.com/p/afe7bfe7a3ee