簡介
最近因為項目的需要,需要實現雷達圖來展示各科目的對題率。
雷達圖的繪制不算復雜,只要按照一定流程來繪制就可以了,其中使用的最多的是path路徑類,使用這個類便於我們繪制出多邊形等效果。
效果圖如下:

使用方式
使用方式很簡單,直接在布局文件里面使用這個控件,記得設置一個合適的大小就可以。
當然也有開放一些public方法,可以進行數據、文本顏色等設置。
/**
* 設置數據
* @param points
*/
public void setData(ArrayList<LastPoint> points){}
/**
* 設置文本
* @param titles
*/
public void setTitles(String[] titles){}
/**
* 設置圈數
* @param count
*/
public void setCount(int count){}
/**
* 設置網格線顏色
* @param color
*/
public void setLineColor(int color){}
/**
* 設置填充區域顏色
* @param color
*/
public void setValueColor(int color){}
/**
* 設置文本顏色
* @param color
*/
public void setTextColor(int color){}
具體實現
一般自定義控件的流程有以下幾個步驟(個人觀點):
* 1、構造函數(初始化)
* 2、onMeasure(測量大小)
* 3、onSizeChanged(確定大小)
* 4、onLayout(子view的位置,如果包含子view的話)
* 5、onDraw(繪制內容)
* 6、暴露給外部的接口
在該控件中,2、4都不用考慮,只要確定了大小(onSizeChanged),就能夠計算出整個布局的中心,雷達圖是以這個中心開始繪制的。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mCenterX = mWidth/2;
mCenterY = mHeight/2;
mRadius = (Math.min(mWidth,mHeight)/2 * 0.9f);
postInvalidate();
}
繪制蜘蛛網圖
/**
* 畫網格
* @param canvas
*/
private void drawLine(Canvas canvas){
Path path = new Path();
//網格線之間的間距
float distance = mRadius / (mCount-1);
for (int i = 0; i < mCount; i++){//外面的網格圖形
float currentRadius = i * distance;//當前半徑
if (i == mCount -1){
//存儲最后一圈網格的點的坐標
mLastPoints.add(new LastPoint(currentRadius,0));
mLastPoints.add(new LastPoint(currentRadius/2,-currentRadius));
mLastPoints.add(new LastPoint(-currentRadius/2,-currentRadius));
mLastPoints.add(new LastPoint(-currentRadius,0));
mLastPoints.add(new LastPoint(-currentRadius/2,currentRadius));
mLastPoints.add(new LastPoint(currentRadius/2,currentRadius));
}
//6個點坐標組成一個網格圖形
path.lineTo(currentRadius,0);
//設置上一次操作的坐標點
path.moveTo(currentRadius,0);
path.lineTo(currentRadius/2,-currentRadius);
path.lineTo(-currentRadius/2,-currentRadius);
path.lineTo(-currentRadius,0);
path.lineTo(-currentRadius/2,currentRadius);
path.lineTo(currentRadius/2,currentRadius);
path.close();
canvas.drawPath(path,mLinePaint);
}
}

繪制從中心到末端的直線
/**
* 畫網格對角線
* @param canvas
*/
private void drawGridLine(Canvas canvas){
Path path = new Path();
for (int i = 0; i < mLastPoints.size(); i++){
path.reset();
LastPoint point = mLastPoints.get(i);
float x = point.x;
float y = point.y;
path.lineTo(x, y);
canvas.drawPath(path, mLinePaint);
}
}

繪制末端文本
由於文本與末端有一定的距離,所以需要加上一定的偏移量;當文本在網格左邊顯示的時候,會與網格有重疊,所以需要先計算文本長度,然后再向左邊偏移對應的距離,這樣就可以解決重疊問題。
/**
* 畫文本
* @param canvas
*/
private void drawText(Canvas canvas){
for (int i = 0; i < mLastPoints.size(); i++){
//文本長度
float dis = mTextPaint.measureText(mTitles[i]);
LastPoint point = mLastPoints.get(i);
float x = point.x;
float y = point.y;
if (i == 2 || i == 3 || i == 4){
//左邊繪制文本:文本顯示在坐標左邊
x = x - dis;
}
if (y > 0){
y+=18;
}
canvas.drawText(mTitles[i],x,y,mTextPaint);
}
}

繪制填充區域
/**
* 畫數據線:填充區域
* @param canvas
*/
private void drawDataLine(Canvas canvas){
if (mDataPoints == null || mDataPoints.size() == 0)
return;
Path path = new Path();
for (int i = 0; i < mDataPoints.size(); i++){
LastPoint point = mDataPoints.get(i);
float x = point.x;
float y = point.y;
path.lineTo(x, y);
if (i == 0){//將上一次操作點移到第一個點坐標,保證最后調用close,形成一個封閉的形狀
path.moveTo(x,y);
}
mValuePaint.setAlpha(255);
//畫小圓點
canvas.drawCircle(x,y,8,mValuePaint);
}
path.close();
mValuePaint.setAlpha(127);
canvas.drawPath(path, mValuePaint);
}

最后,貼上完整的源代碼:
package com.ha.cjy.myproject.view.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import java.util.ArrayList;
/**
* 自定義雷達圖
* 1、構造函數(初始化)
* 2、onMeasure(測量大小)
* 3、onSizeChanged(確定大小)
* 4、onLayout(子view的位置,如果包含子view的話)
* 5、onDraw(繪制內容)
* 6、暴露給外部的接口
* Created by cjy on 17/8/15.
*/
public class CustomRadarView extends View {
//寬度
private int mWidth;
//高度
private int mHeight;
//原點坐標
private int mCenterX;
private int mCenterY;
//網格半徑
private float mRadius;
//網格圈數
private int mCount = 10;
//Paint
private Paint mLinePaint;
private Paint mValuePaint;
private Paint mTextPaint;
//顏色值
private int mLineColor = Color.GRAY;
private int mValueColor = Color.BLUE;
private int mTextColor = Color.BLACK;
//最后一圈網格坐標點集合
private ArrayList<LastPoint> mLastPoints = new ArrayList<LastPoint>();
//數據坐標點集合
private ArrayList<LastPoint> mDataPoints = new ArrayList<LastPoint>();
//文本集合
private String[] mTitles = new String[]{"科目A","科目A1","科目1","科目D","科目E","科目F"};
public CustomRadarView(Context context) {
super(context);
init(context);
}
public CustomRadarView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public CustomRadarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
//中心坐標
mCenterX = mWidth/2;
mCenterY = mHeight/2;
mRadius = (Math.min(mWidth,mHeight)/2 * 0.9f);
postInvalidate();
}
private void init(Context context){
mLinePaint = new Paint();
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeWidth(2);
mLinePaint.setColor(mLineColor);
mValuePaint = new Paint();
mValuePaint.setStyle(Paint.Style.FILL_AND_STROKE);
mValuePaint.setStrokeWidth(2);
mValuePaint.setColor(mValueColor);
mTextPaint = new Paint();
mTextPaint.setStyle(Paint.Style.STROKE);
mTextPaint.setStrokeWidth(2);
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(28);
mTextPaint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//移動原點坐標
canvas.translate(mCenterX,mCenterY);
//畫網格線
drawLine(canvas);
//畫網格對角線
drawGridLine(canvas);
//畫文本
drawText(canvas);
//畫數據線
drawDataLine(canvas);
}
/**
* 畫網格
* @param canvas
*/
private void drawLine(Canvas canvas){
Path path = new Path();
//網格線之間的間距
float distance = mRadius / (mCount-1);
for (int i = 0; i < mCount; i++){//外面的網格圖形
float currentRadius = i * distance;//當前半徑
if (i == mCount -1){
//存儲最后一圈網格的點的坐標
mLastPoints.add(new LastPoint(currentRadius,0));
mLastPoints.add(new LastPoint(currentRadius/2,-currentRadius));
mLastPoints.add(new LastPoint(-currentRadius/2,-currentRadius));
mLastPoints.add(new LastPoint(-currentRadius,0));
mLastPoints.add(new LastPoint(-currentRadius/2,currentRadius));
mLastPoints.add(new LastPoint(currentRadius/2,currentRadius));
}
//6個點坐標組成一個網格圖形
path.lineTo(currentRadius,0);
//設置上一次操作的坐標點
path.moveTo(currentRadius,0);
path.lineTo(currentRadius/2,-currentRadius);
path.lineTo(-currentRadius/2,-currentRadius);
path.lineTo(-currentRadius,0);
path.lineTo(-currentRadius/2,currentRadius);
path.lineTo(currentRadius/2,currentRadius);
path.close();
canvas.drawPath(path,mLinePaint);
}
}
/**
* 畫網格對角線
* @param canvas
*/
private void drawGridLine(Canvas canvas){
Path path = new Path();
for (int i = 0; i < mLastPoints.size(); i++){
path.reset();
LastPoint point = mLastPoints.get(i);
float x = point.x;
float y = point.y;
path.lineTo(x, y);
canvas.drawPath(path, mLinePaint);
}
}
/**
* 畫文本
* @param canvas
*/
private void drawText(Canvas canvas){
for (int i = 0; i < mLastPoints.size(); i++){
//文本長度
float dis = mTextPaint.measureText(mTitles[i]);
LastPoint point = mLastPoints.get(i);
float x = point.x;
float y = point.y;
if (i == 2 || i == 3 || i == 4){
//左邊繪制文本:文本顯示在坐標左邊
x = x - dis;
}
if (y > 0){
y+=18;
}
canvas.drawText(mTitles[i],x,y,mTextPaint);
}
}
/**
* 畫數據線:填充區域
* @param canvas
*/
private void drawDataLine(Canvas canvas){
if (mDataPoints == null || mDataPoints.size() == 0)
return;
Path path = new Path();
for (int i = 0; i < mDataPoints.size(); i++){
LastPoint point = mDataPoints.get(i);
float x = point.x;
float y = point.y;
path.lineTo(x, y);
if (i == 0){//將上一次操作點移到第一個點坐標,保證最后調用close,形成一個封閉的形狀
path.moveTo(x,y);
}
mValuePaint.setAlpha(255);
//畫小圓點
canvas.drawCircle(x,y,8,mValuePaint);
}
path.close();
mValuePaint.setAlpha(127);
canvas.drawPath(path, mValuePaint);
}
/**
* 設置數據
* @param points
*/
public void setData(ArrayList<LastPoint> points){
mDataPoints = points;
invalidate();
}
/**
* 設置文本
* @param titles
*/
public void setTitles(String[] titles){
mTitles = titles;
invalidate();
}
/**
* 設置圈數
* @param count
*/
public void setCount(int count){
mCount = count;
invalidate();
}
/**
* 設置網格線顏色
* @param color
*/
public void setLineColor(int color){
mLineColor = color;
mLinePaint.setColor(mLineColor);
invalidate();
}
/**
* 設置填充區域顏色
* @param color
*/
public void setValueColor(int color){
mValueColor = color;
mValuePaint.setColor(mValueColor);
invalidate();
}
/**
* 設置文本顏色
* @param color
*/
public void setTextColor(int color){
mTextColor = color;
mTextPaint.setColor(mTextColor);
invalidate();
}
/**
* 坐標點
*/
public static class LastPoint {
private float x;
private float y;
public LastPoint(float x, float y) {
this.x = x;
this.y = y;
}
}
}
