Android Path 最佳實踐之繪制雷達圖

第一步:繪制蜘蛛網(wǎng)絡

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發(fā)生改變,重新繪制
    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度=1*PI/180   360度=2*PI   那么我們每旋轉一次的角度為2*PI/內角個數(shù)
    //中心與相鄰兩個內角相連的夾角角度
    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);
    }

繪制蜘蛛網(wǎng)絡其實就是繪制指定邊數(shù)的正多邊形,這一步比較簡單,比較難的可能就是每個頂點的算法,相關注釋我都寫了,還有一張來自互聯(lián)網(wǎng)的圖以助于思考,如下:

多邊形夾角示意圖

繪制出的多邊形成品如下:

多邊形效果.gif

動畫效果只是寫了 set 方法,用 handler 實現(xiàn),代碼如下:

//設置數(shù)值種類
public void setCount(int count) {
    this.count = count;
    postInvalidate();
}

//設置蜘蛛網(wǎng)顏色
public void setMainPaint(Paint mainPaint) {
    this.mainPaint = mainPaint;
    postInvalidate();
}

調用方法:

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);
    }
}

這一步比較簡單,就是將中心點和各個頂點連接起來,效果如下:

多邊形效果.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);
        }

    }

}

效果如下:

image.png

第四步:繪制覆蓋區(qū)域

要繪制覆蓋區(qū)域,首先要指定最大值和每個分類的具體數(shù)值,有了這些數(shù)值之后,就可以繪制了。
代碼如下:

/**
 * 繪制覆蓋區(qū)域
 */
private void drawRegion(Canvas canvas){
    valuePaint.setAlpha(255);
    Path path=new Path();
    for (int i = 0; i < count; i++) {
        //計算該數(shù)值與最大值比例
        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);
    }
    //閉合覆蓋區(qū)域
    path.close();
    valuePaint.setStyle(Paint.Style.STROKE);
    //繪制覆蓋區(qū)域外的連線
    canvas.drawPath(path, valuePaint);
    //填充覆蓋區(qū)域
    valuePaint.setAlpha(128);
    valuePaint.setStyle(Paint.Style.FILL);
    canvas.drawPath(path,valuePaint);
}

看一下效果:

image.png

再來看一下動態(tài)的效果吧:

多邊形效果.gif

總結

終于完成了,全部代碼在下面:

Android雷達圖全部代碼

主要是參考 crazy__chen 大神的博客,鏈接貼在下面,做了一遍其實還蠻簡單的,這個控件還有很多不完善的,如果實際使用需要改善的地方還有很多,如果有不足希望大家可以告訴我,謝謝??!

參考資料

Android雷達圖(蜘蛛網(wǎng)圖)繪制

Path之基本操作

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容