# 自定义View之LOL能力七星图

## 本文目的

1. 巩固自定义控件的基础知识以及正多边形的绘制，熟悉绘制流程。
2. 复习了高中的一点数学几何知识。

## 源码地址

https://github.com/qq908323236/AbilityMapView

x=rcosθ
y=rsinθ

## 具体实现

### AbilityBean

public class AbilityBean {

//有哪个些能力
public static final String[] abilitys = {"击杀", "生存", "助攻", "物理", "魔法", "防御", "金钱"};

//每个能力的值，范围0~100，单位%
private int kill;
private int survival;
private int assist;
private int ap;
private int defense;
private int money;

public AbilityBean(int kill, int survival, int assist, int ad, int ap, int defense, int money) {
this.kill = kill;
this.survival = survival;
this.assist = assist;
this.ap = ap;
this.defense = defense;
this.money = money;
}

public static String[] getAbilitys() {
return abilitys;
}

public int getKill() {
return kill;
}

public void setKill(int kill) {
this.kill = kill;
}

public int getSurvival() {
return survival;
}

public void setSurvival(int survival) {
this.survival = survival;
}

public int getAssist() {
return assist;
}

public void setAssist(int assist) {
this.assist = assist;
}

}

}

public int getAp() {
return ap;
}

public void setAp(int ap) {
this.ap = ap;
}

public int getDefense() {
return defense;
}

public void setDefense(int defense) {
this.defense = defense;
}

public int getMoney() {
return money;
}

public void setMoney(int money) {
this.money = money;
}

public int[] getAllAbility() {
int[] allAbility = {kill, survival, assist, ad, ap, defense, money};
return allAbility;
}

### 控件参数

    private AbilityBean data;  //元数据
private int n;    //边的数量或者能力的个数
private float R;    //最外圈的半径，顶点到中心点的距离
private int intervalCount; //间隔数量，就把半径分为几段
private float angle;     //两条顶点到中线点的线之间的角度

private Paint linePaint;  //画线的笔
private Paint textPaint;  //画文字的笔

private int viewHeight;   //控件宽度
private int viewWidth;    //控件高度
private ArrayList<ArrayList<PointF>> pointsArrayList;  //存储多边形顶点数组的数组
private ArrayList<PointF> abilityPoints;   //存储能力点的数组

### 初始化

android:hardwareAccelerated=”false”

    public AbilityMapView(Context context) {
//这地方改为this,使得不管怎么初始化都会进入第三个构造函数中
this(context, null);
}

public AbilityMapView(Context context, @Nullable AttributeSet attrs) {
//这地方改为this,使得不管怎么初始化都会进入第三个构造函数中
this(context, attrs, 0);
}

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

initSize(context);

initPoints();

initPaint(context);
}

    /**
* 初始化一些固定数据
*
* @param context
*/
private void initSize(Context context) {
n = 7;  //七条边
R = dp2pxF(context, 100);  //半径暂时设为100dp
intervalCount = 4;   //有四层
angle = (float) ((2 * Math.PI) / n);     //一周是2π,这里用π，因为进制的问题，不能用360度,画出来会有问题

//拿到屏幕的宽高，单位是像素
int screenWidth = getResources().getDisplayMetrics().widthPixels;
//控件设置为正方向
viewWidth = screenWidth;
viewHeight = screenWidth;

}

/**
* 初始化多边形的所有点 每一圈7个点，有4圈
*/
private void initPoints() {
//一个数组中每个元素又一是一个点数组,有几个多边形就有几个数组
pointsArrayList = new ArrayList<>();
float x;
float y;
for (int i = 0; i < intervalCount; i++) {
//创建一个存储点的数组
ArrayList<PointF> points = new ArrayList<>();
for (int j = 0; j < n; j++) {
float r = R * ((float) (4 - i) / intervalCount);  //每一圈的半径都按比例减少
//这里减去Math.PI / 2 是为了让多边形逆时针旋转90度，所以后面的所有用到cos,sin的都要减
x = (float) (r * Math.cos(j * angle - Math.PI / 2));
y = (float) (r * Math.sin(j * angle - Math.PI / 2));
}
}
}

/**
* 初始化画笔
*
* @param context
*/
private void initPaint(Context context) {
//画线的笔
linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//设置线宽度
linePaint.setStrokeWidth(dp2px(context, 1f));

//画文字的笔
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextAlign(Paint.Align.CENTER);  //设置文字居中
textPaint.setColor(Color.BLACK);
textPaint.setTextSize(sp2pxF(context, 14f));
}

initPoints中，是初始化了多边形的坐标，具体逻辑就是用到了开篇讲的数学知识，相信读者配合注释一眼就能看明白，需要注意的是这里是两个循环，并且把点装在了二维数组中（就是数组中的元素还是数组），因为我们这里要绘制4个多边形。而每一层的多边形中的半径都按比例减小。

    /**
* 传入元数据
*
* @param data
*/
public void setData(AbilityBean data) {
if (data == null) {
return;
}
this.data = data;

//View本身调用迫使view重画
invalidate();
}

### 重写onMeasure、onSizeChanged

    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

//设置控件的最终视图大小(宽高)
setMeasuredDimension(viewWidth, viewHeight);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initSize(getContext());
}

### onDraw

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

//把画布的原点移动到控件的中心点
canvas.translate(viewWidth / 2, viewHeight / 2);

drawPolygon(canvas);

drawOutLine(canvas);

drawAbilityLine(canvas);

drawAbilityText(canvas);

}

### 1.drawPolygon(canvas)

    /**
* 绘制多边形框,每一层都绘制
*
* @param canvas
*/
private void drawPolygon(Canvas canvas) {
canvas.save();//保存画布当前状态(平移、放缩、旋转、裁剪等),和canvas.restore()配合使用

linePaint.setStyle(Paint.Style.FILL_AND_STROKE);  //设置为填充且描边

Path path = new Path();  //路径
for (int i = 0; i < intervalCount; i++) {  //循环、一层一层的绘制
//每一层的颜色都都不同
switch (i) {
case 0:
linePaint.setColor(Color.parseColor("#D4F0F3"));
break;
case 1:
linePaint.setColor(Color.parseColor("#99DCE2"));
break;
case 2:
linePaint.setColor(Color.parseColor("#56C1C7"));
break;
case 3:
linePaint.setColor(Color.parseColor("#278891"));
break;
}
for (int j = 0; j < n; j++) {   //每一层有n个点
float x = pointsArrayList.get(i).get(j).x;
float y = pointsArrayList.get(i).get(j).y;
if (j == 0) {
//如果是每层的第一个点就把path的起点设置为这个点
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
path.close();  //设置为闭合的
canvas.drawPath(path, linePaint);
path.reset();   //清除path存储的路径
}

canvas.restore();
}

### 2.drawOutLine(Canvas canvas)

    /**
* 画轮廓线
* 1.先画最外面的多边形轮廓
* 2.再画顶点到中心的线
*
* @param canvas
*/
private void drawOutLine(Canvas canvas) {
canvas.save();//保存画布当前状态(平移、放缩、旋转、裁剪等),和canvas.restore()配合使用

linePaint.setColor(Color.parseColor("#99DCE2"));
linePaint.setStyle(Paint.Style.STROKE);  //设置空心的

//先画最外面的多边形轮廓
Path path = new Path();  //路径
for (int i = 0; i < n; i++) {
//只需要第一组的点
float x = pointsArrayList.get(0).get(i).x;
float y = pointsArrayList.get(0).get(i).y;
if (i == 0) {
//如果是第一个点就把path的起点设置为这个点
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
path.close(); //闭合路径
canvas.drawPath(path, linePaint);

//再画顶点到中心的线
for (int i = 0; i < n; i++) {
float x = pointsArrayList.get(0).get(i).x;
float y = pointsArrayList.get(0).get(i).y;
canvas.drawLine(0, 0, x, y, linePaint); //起点都是中心点
}

canvas.restore();
}

### 3.drawAbilityLine(Canvas canvas)

    /**
* 画能力线
*
* @param canvas
*/
private void drawAbilityLine(Canvas canvas) {
canvas.save();

//先把能力点初始化出来
abilityPoints = new ArrayList<>();
int[] allAbility = data.getAllAbility();
for (int i = 0; i < n; i++) {
float r = R * (allAbility[i] / 100.0f);  //能力值/100再乘以半径就是所占的比例
float x = (float) (r * Math.cos(i * angle - Math.PI / 2));
float y = (float) (r * Math.sin(i * angle - Math.PI / 2));
}

linePaint.setStrokeWidth(dp2px(getContext(), 2f));
linePaint.setColor(Color.parseColor("#E96153"));
linePaint.setStyle(Paint.Style.STROKE);  //设置空心的

Path path = new Path();  //路径
for (int i = 0; i < n; i++) {
float x = abilityPoints.get(i).x;
float y = abilityPoints.get(i).y;
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
path.close();   //别忘了闭合

canvas.drawPath(path, linePaint);

canvas.restore();
}

### 3.drawAbilityText(Canvas canvas)

    /**
* 画能力描述的文字
*
* @param canvas
*/
private void drawAbilityText(Canvas canvas) {
canvas.save();
//先计算出坐标来
ArrayList<PointF> textPoints = new ArrayList<>();
for (int i = 0; i < n; i++) {
float r = R + dp2pxF(getContext(), 15f);
float x = (float) (r * Math.cos(i * angle - Math.PI / 2));
float y = (float) (r * Math.sin(i * angle - Math.PI / 2));
}
//拿到字体测量器
Paint.FontMetrics metrics = textPaint.getFontMetrics();
String[] abilitys = AbilityBean.getAbilitys();
for (int i = 0; i < n; i++) {
float x = textPoints.get(i).x;
//ascent:上坡度，是文字的基线到文字的最高处的距离
//descent:下坡度,，文字的基线到文字的最低处的距离
float y = textPoints.get(i).y - (metrics.ascent + metrics.descent) / 2;
canvas.drawText(abilitys[i], x, y, textPaint);
}

canvas.restore();
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.fu.abilitymapview.MainActivity">

<com.fu.abilitymapview.AbilityMapView
android:id="@+id/ability_map_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</LinearLayout>

MainActivity

public class MainActivity extends AppCompatActivity {

private AbilityMapView abilitymapview;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.abilitymapview = (AbilityMapView) findViewById(R.id.ability_map_view);
abilitymapview.setData(new AbilityBean(65, 70, 80, 70, 80, 80, 80));
}
}

## 补充说明

### 2.辅助坐标轴

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

//把画布的原点移动到控件的中心点
canvas.translate(viewWidth / 2, viewHeight / 2);

drawPolygon(canvas);

drawOutLine(canvas);

drawAbilityLine(canvas);

drawAbilityText(canvas);

//坐标轴x,y 辅助用
linePaint.setColor(Color.RED);
canvas.drawLine(-(viewWidth / 2), 0, viewWidth / 2, 0, linePaint);
canvas.drawLine(0, -(viewWidth / 2), 0, viewWidth / 2, linePaint);
}

• 评论

8

• 上一篇
• 下一篇