我们要实现一个战斗力的网状图,可以随意改变网状图的边数,从外面传入边数后可以自动调节各个属性值,图示为正4、7、8、13边形的战力图表。
根据图中显示,整体实现步骤大致可以分为4步:
画正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边形,各个顶点坐标是怎么求的
六边形坐标计算:
右上角的顶点坐标:
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);
}
}