在Java里绘制箭头

绘制箭头

在java里,如何绘制一个箭头呢?

箭头的定义

从上面可以看出来,一个箭头可以由6个参数来决定:

  • 箭头的长度 arrowLength (不使用)
  • 箭头的角度 angle
  • 箭头的斜边长 sideLength
  • 箭头的内角度 innerAngle
  • 头部的宽度 headWidth
  • 尾部的宽度 tailWidth

注意:为了保持箭头的形状,一般不成比例的放大缩小箭头,而是根据需要只调整箭头的长度,不改变箭头的形状。成比例缩放箭头效果如下:

所以 箭头的长度arrowLength在计算的时候不会使用

这几个参数决定好以后,整个箭头的形状就可以由4个点来决定了:

1
2
3
4
(x2,y2)
(x3,y3)
(x5,y5)
(x6,y6)

具体的计算可以参考下面的代码。

利用Matrix功能,就可以把这个箭头绘制到任何想要的起点和终点上了。

java的绘制代码

示例代码:

https://github.com/zfdang/chinese-chess-android/blob/master/app/src/main/java/com/zfdang/chess/utils/ArrowShape.java

生成Path曲线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.zfdang.chess.utils;

import android.graphics.Matrix;
import android.graphics.Path;

/*
* ArrowShape.java
* 设置箭头的参数,然后生成箭头的Path
* 具体的参数可以参考这个文档:
* https://github.com/zfdang/chinese-chess-android/blob/master/docs/%E7%BB%98%E5%88%B6%E7%AE%AD%E5%A4%B4.md
* Created by zfdang on 2018-03-25
*/
public class ArrowShape {
float angle;
float innerAngle;
float sideLength;
float headWidth;
float tailWidth;

public ArrowShape(float angle, float innerAngle, float length, float sideLength, float headWidth, float tailWidth) {
this.angle = angle;
this.innerAngle = innerAngle;
this.sideLength = sideLength;
this.headWidth = headWidth;
this.tailWidth = tailWidth;
}

public ArrowShape() {
this.angle = 60;
this.innerAngle = 120;
this.sideLength = 70;
this.headWidth = 18;
this.tailWidth = 12;
}

public void getTransformedPath(Path path, float x0, float y0, float x1, float y1) {

double angle1 = this.angle / 360 * Math.PI;
double angle2 = this.innerAngle / 360 * Math.PI;
float sinv1 = (float) Math.sin(angle1);
float cosv1 = (float) Math.cos(angle1);
float tanv2 = (float) Math.tan(angle2);

// 直接设置箭头的长度为目标长度,避免缩放时箭头变形
float x2 = (float) Math.hypot(x1 - x0, y1 - y0);
float y2 = 0;

// (x3, y3)
float x3 = (float) (x2 - sideLength * cosv1);
float y3 = (float) (y2 - sideLength * sinv1);

// (x5, y5)
float x5 = (float) (x3 + sideLength * sinv1 / tanv2 - headWidth / 2 / tanv2);
float y5 = -headWidth / 2;

// (x6, y6)
float x6 = 0;
float y6 = -tailWidth / 2;

path.reset();
path.moveTo(x2, y2);
path.lineTo(x3, y3);
path.lineTo(x5, y5);
path.lineTo(x6, y6);
path.lineTo(x6, -y6);
path.lineTo(x5, -y5);
path.lineTo(x3, -y3);
path.close();

Matrix mtx = new Matrix();
mtx.postRotate((float) (Math.atan2(y1 - y0, x1 - x0) * 180 / Math.PI));
mtx.postTranslate(x0, y0);
path.transform(mtx);
}
}

绘制曲线

https://github.com/zfdang/chinese-chess-android/blob/master/app/src/main/java/com/zfdang/chess/views/ChessView.java
1
2
3
4
5
6
7
void DrawArrow(Canvas canvas, XYCoord crd0, XYCoord crd1, Paint p) {
ArrowShape arrow = new ArrowShape();
Path path = new Path();
arrow.getTransformedPath(path, crd0.x, crd0.y, crd1.x, crd1.y);

canvas.drawPath(path, p);
}