Android畫正N邊形戰力圖

總體實現的目標如下:

我們要實現一個戰鬥力的網狀圖,可以隨意改變網狀圖的邊數,從外面傳入邊數後可以自動調節各個屬性值,圖示爲正4、7、8、13邊形的戰力圖表。

根據圖中顯示,整體實現步驟大致可以分爲4步:

  • 步驟一:首先是畫正N邊形
  • 步驟二:從中心點到N邊形各個頂點的連線
  • 步驟三:畫戰力區域
  • 步驟四:畫戰力值文字

步驟一:首先是要畫一個正N邊形,圖例爲正六邊形:

畫正N邊形最重要的就是求出N邊形的每個頂點座標,然後將這些頂點座標連接起來就可以了。

延伸一下:我們可以將問題轉化爲求圓周上的每個點的座標,首先要學習下Math.sin(弧度)、Math.cos(弧度),注意這裏的參數是弧度而非角的度數。

弧度的計算公式爲: 角度*(PI/180)

30° 角度 的弧度 = 30 * (PI/180)

如何得到圓上每個點的座標?

解決思路:根據三角形的正玄、餘弦來得值;

假設一個圓的圓心座標是(a,b),半徑爲r,

則圓上每個點的: 
X座標=a + Math.sin(角度 * (Math.PI / 180)) * r ;
Y座標=b + Math.cos(角數 * (Math.PI / 180)) * r ;

例子:求時鐘的秒針轉動一圈的軌跡?

假設秒針的初始值(起點)爲12點鐘方向,圓心的座標爲(a,b)。

解決思路:一分鐘爲60秒,一個圓爲360°,所以平均每秒的轉動角度爲 360°/60 = 6°; 

for (int times = 0; times < 60; times++) {

       int hudu = 6 * (Math.PI / 180) *  times;

       int X = a + Math.sin(hudu) * r;

       int Y = b - Math.cos(hudu) * r    //  注意此處是“-”號,因爲我們要得到的Y是相對於(0,0)而言的。

}

1、本例是以“12點爲起點, 角度增大時爲順時針方向“,求X座標和Y座標的方法是:
X座標=a + Math.sin(角度 * (Math.PI / 180)) * r ;
Y座標=b - Math.cos(角數 * (Math.PI / 180)) * r ; 
 

2、一般“3點爲起點, 角度增大時爲逆時針方向“,求X座標和Y座標的方法是:
X座標 = a + Math.cos(角度 * (Math.PI / 180)) * r;    
Y座標 = b - Math.sin(角度 * (Math.PI / 180)) * r;    

明白了圓周上各個點的座標求法,下面看正N邊形,各個頂點座標是怎麼求的

六邊形座標計算:

polygon

右上角的頂點座標:

x點座標:中心點 + R*sin(60°)

y點座標:中心點 - R*cos(60°)

整體代碼:

import android.content.Context;
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 androidx.annotation.Nullable;

public class SpiderView extends View {

    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    int width;
    int height;
    //N邊形的邊數
    int edges = 6;
    //根據邊數求得每個頂點對應的度數
    double degrees = 360 / edges;
    //根據度數,轉化爲弧度
    double hudu = (Math.PI / 180) * degrees;

    {
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(3);
        paint.setColor(Color.BLACK);
    }

    public SpiderView(Context context) {
        super(context);
    }

    public SpiderView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public SpiderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        drawPolygon(canvas, 500.0F);
        drawPolygon(canvas, 400.0F);
        drawPolygon(canvas, 300.0F);
        drawPolygon(canvas, 200.0F);
        drawPolygon(canvas, 100.0F);
    }

    private void drawPolygon(Canvas canvas, float radius) {
        Path path = new Path();
        path.moveTo(width / 2, height / 2 - radius);//從上面的頂點出發

        float endx, endy;

        for (int i = 0; i < edges; i++) {
            endx = (float) (width / 2 + radius * Math.sin(hudu * i));
            endy = (float) (height / 2 - radius * Math.cos(hudu * i));
            path.lineTo(endx, endy);
        }
        path.close();
        canvas.drawPath(path, paint);
    }
}

上面代碼中,只需要改變正N邊形的邊數edges即可。

步驟二:從中心點到各個頂點的連線

思路和畫正N邊形一樣的,只是這裏每畫完一條射線都要將起始點挪動到原始點去,然後再畫從原始點到各個頂點的下一條射線。

/**
     * 從中心點到各個頂點畫一條線
     * @param canvas
     * @param radius
     */
    private void drawLines(Canvas canvas, float radius) {
        //從中心點出發
        Path path = new Path();
        path.moveTo(width / 2, height / 2);
        float endx, endy;
        for (int i = 0; i < edges; i++) {
            endx = (float) (width / 2 + radius * Math.sin(hudu * i));
            endy = (float) (height / 2 - radius * Math.cos(hudu * i));
            path.lineTo(endx, endy);
            canvas.drawPath(path, radialLinesPaint);
            //畫完一條線後,重置起點在中心點,再畫下一條直線
            endx = width/2;
            endy = height/2;
            path.moveTo(endx, endy);
        }
    }

步驟三:畫戰力區域

戰力區域思路和畫正N邊形是一致的,只是這裏每個點是半徑的0~1的比率取值即可,這裏通過rankData取值:

/**
     * 畫戰力值區域
     *
     * @param canvas
     * @param radius
     */
    private void drawRanks(Canvas canvas, float radius) {
        Path path = new Path();
        float endx, endy;
        for (int i = 0; i < edges; i++) {
            endx = (float) (width / 2 + radius * Math.sin(hudu * i) * rankData[i]);
            endy = (float) (height / 2 - radius * Math.cos(hudu * i) * rankData[i]);
            if (i == 0) {
                path.moveTo(width / 2, (float) (height / 2 - radius * 0.5));
            } else {
                path.lineTo(endx, endy);
            }
        }
        path.close();
        canvas.drawPath(path, rankPaint);
    }

步驟四:畫戰力值文字:

思路和上面兩個一致,這裏只是需要調整文字距離最外圈的距離:

/**
     * 畫戰力文字
     *
     * @param canvas
     * @param radius
     */
    private void drawRankText(Canvas canvas, float radius) {
        float endx, endy;
        Rect bounds = new Rect();
        for (int i = 0; i < edges; i++) {
            rankTextPaint.getTextBounds(rankText[i], 0, rankText[i].length(), bounds);
            endx = (float) (width / 2 + radius * 1.2 * Math.sin(hudu * i) - (bounds.right - bounds.left) / 2);
            endy = (float) (height / 2 - radius * 1.1 * Math.cos(hudu * i) + (bounds.bottom - bounds.top) / 2);
            canvas.drawText(rankText[i], endx, endy, rankTextPaint);
        }
    }

最後看下整體的實現效果,代碼中改變下edges的數目即可變成正N邊形:

戰力值的所有代碼:

package com.test.customview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

public class SpiderView extends View {

    //正N邊形邊線
    Paint edgesPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    //中心發出的射線
    Paint radialLinesPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    //等級線
    Paint rankPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    //等級文字
    Paint rankTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    //自定義view的寬高
    float width, height;
    //戰力值數據
    private double[] rankData = {0.5, 0.2, 0.8, 0.6, 0.9, 0.6, 0.2, 0.8, 0.4, 0.9, 0.1, 0.7, 0.2, 0.9};

    //戰力種類
    private String[] rankText = {"擊殺", "助攻", "金錢", "物理", "防禦", "魔法", "裝備", "血量", "魔抗", "穿甲", "綜合", "裝甲", "魔抗"};

    //N邊形的邊數
    int edges = 7;
    //根據邊數求得每個頂點對應的度數
    double degrees = 360 / edges;
    //根據度數,轉化爲弧度
    double hudu = (Math.PI / 180) * degrees;

    {
        edgesPaint.setStyle(Paint.Style.STROKE);
        edgesPaint.setStrokeWidth(3);
        edgesPaint.setColor(Color.BLACK);

        radialLinesPaint.setStyle(Paint.Style.STROKE);
        radialLinesPaint.setStrokeWidth(2);
        radialLinesPaint.setColor(Color.BLUE);

        rankPaint.setStyle(Paint.Style.STROKE);
        rankPaint.setStrokeWidth(10);
        rankPaint.setColor(Color.RED);

        rankTextPaint.setStyle(Paint.Style.FILL);
        rankTextPaint.setStrokeWidth(1);
        rankTextPaint.setColor(Color.BLACK);
        rankTextPaint.setTextSize(50);
    }

    public SpiderView(Context context) {
        super(context);
    }

    public SpiderView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public SpiderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //正N邊形個數
        edgesPaint.setStyle(Paint.Style.FILL);
        edgesPaint.setColor(Color.parseColor("#c3e3e5"));
        drawPolygon(canvas, 400.0F);
        edgesPaint.setColor(Color.parseColor("#85cdd4"));
        drawPolygon(canvas, 300.0F);
        edgesPaint.setColor(Color.parseColor("#48afb6"));
        drawPolygon(canvas, 200.0F);
        edgesPaint.setColor(Color.parseColor("#22737b"));
        drawPolygon(canvas, 100.0F);

        //從中心點到各個頂點的射線
        drawLines(canvas, 400);

        //畫戰力值區域
        drawRanks(canvas, 400);

        //畫戰力文字
        drawRankText(canvas, 400);
    }

    /**
     * 畫戰力值區域
     *
     * @param canvas
     * @param radius
     */
    private void drawRanks(Canvas canvas, float radius) {
        Path path = new Path();
        float endx, endy;
        for (int i = 0; i < edges; i++) {
            endx = (float) (width / 2 + radius * Math.sin(hudu * i) * rankData[i]);
            endy = (float) (height / 2 - radius * Math.cos(hudu * i) * rankData[i]);
            if (i == 0) {
                path.moveTo(width / 2, (float) (height / 2 - radius * 0.5));
            } else {
                path.lineTo(endx, endy);
            }
        }
        path.close();
        canvas.drawPath(path, rankPaint);
    }

    /**
     * 畫戰力文字
     *
     * @param canvas
     * @param radius
     */
    private void drawRankText(Canvas canvas, float radius) {
        float endx, endy;
        Rect bounds = new Rect();
        for (int i = 0; i < edges; i++) {
            rankTextPaint.getTextBounds(rankText[i], 0, rankText[i].length(), bounds);
            endx = (float) (width / 2 + radius * 1.2 * Math.sin(hudu * i) - (bounds.right - bounds.left) / 2);
            endy = (float) (height / 2 - radius * 1.1 * Math.cos(hudu * i) + (bounds.bottom - bounds.top) / 2);
            canvas.drawText(rankText[i], endx, endy, rankTextPaint);
        }
    }

    /**
     * 從中心點到各個頂點的射線
     *
     * @param canvas
     * @param radius
     */
    private void drawLines(Canvas canvas, float radius) {
        //從中心點出發
        Path path = new Path();
        path.moveTo(width / 2, height / 2);
        float endx, endy;
        for (int i = 0; i < edges; i++) {
            endx = (float) (width / 2 + radius * Math.sin(hudu * i));
            endy = (float) (height / 2 - radius * Math.cos(hudu * i));
            path.lineTo(endx, endy);
            canvas.drawPath(path, radialLinesPaint);
            //畫完一條線後,重置起點在中心點,再畫下一條直線
            endx = width / 2;
            endy = height / 2;
            path.moveTo(endx, endy);
        }
    }

    /**
     * 畫正N邊形
     *
     * @param canvas
     * @param radius
     */
    private void drawPolygon(Canvas canvas, float radius) {
        //從上面的頂點出發
        Path path = new Path();
        path.moveTo(width / 2, height / 2 - radius);

        float endx, endy;
        for (int i = 0; i < edges; i++) {
            endx = (float) (width / 2 + radius * Math.sin(hudu * i));
            endy = (float) (height / 2 - radius * Math.cos(hudu * i));
            path.lineTo(endx, endy);
        }
        path.close();
        canvas.drawPath(path, edgesPaint);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章