Path是一个工具类,用来记录线条的轨迹路径,然后通过绘制轨迹路径,可以得到各种各样的图案,而PathMeasure是用来对Path进行测量的工具,再Path的运用中,运用最多的就是贝塞尔曲线,也是本文的重点
贝塞尔曲线
贝塞尔曲线就是这样的一条曲线,它是依据四个位置任意的点坐标绘制出的一条光滑曲线
线性公式
给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。这条线由下式给出:
且其等同于线性插值
二次方公式
二次方贝兹曲线的路径由给定点P0、P1、P2的函数B(t)追踪:
TrueType字型就运用了以贝兹样条组成的二次贝兹曲线
三次方公式
P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”
曲线的参数形式为:
现代的成象系统,如PostScript、Asymptote和Metafont,运用了以贝兹样条组成的三次贝兹曲线,用来描绘曲线轮廓
一般参数公式
阶贝兹曲线可如下推断。给定点P0、P1、…、Pn,其贝兹曲线即:
如上公式可如下递归表达: 用表示由点P0、P1、…、Pn所决定的贝兹曲线
公式说明
- 开始于P0并结束于Pn的曲线,即所谓的端点插值法属性
- 曲线是直线的充分必要条件是所有的控制点都位在曲线上。同样的,贝塞尔曲线是直线的充分必要条件是控制点共线
- 曲线的起始点(结束点)相切于贝塞尔多边形的第一节(最后一节)
- 一条曲线可在任意点切割成两条或任意多条子曲线,每一条子曲线仍是贝塞尔曲线
- 一些看似简单的曲线(如圆)无法以贝塞尔曲线精确的描述,或分段成贝塞尔曲线(虽然当每个内部控制点对单位圆上的外部控制点水平或垂直的的距离为时,分成四段的贝兹曲线,可以小于千分之一的最大半径误差近似于圆)
- 位于固定偏移量的曲线(来自给定的贝塞尔曲线),又称作偏移曲线(假平行于原来的曲线,如两条铁轨之间的偏移)无法以贝兹曲线精确的形成(某些琐屑实例除外)。无论如何,现存的启发法通常可为实际用途中给出近似值
Android中的贝塞尔曲线使用
首先得到贝塞尔曲线的图像及要素
然后使用Path绘制
Path工具类1
Path path = new Path();
二阶贝塞尔:其参数第一对是控制点,第二对是结束点1
path.quadTo();
e.g.1
2
3
4path.moveTo(100, 400);
path.quadTo(200, 0, 500, 400);
path.quadTo(700, 600, 900, 400);
canvas.drawPath(path, paint);
三阶贝塞尔1
path.cubicTo();
e.g.1
2
3path.moveTo(100, 1000);
path.cubicTo(300, 900, 600, 1200, 900, 1000);
canvas.drawPath(path, paint);
生成二阶及三阶贝塞尔曲线如下图
贝塞尔曲线实现波形图
在onDraw()
里面绘制1
2
3
4
5
6
7
8
9
10
11
12
13int waveLen = 200;
int originY = 400;
path.moveTo(-waveLen, originY);
for (int i = -waveLen; i < getWidth() + waveLen; i += waveLen) {
//使用quadTo,绝对位置
//path.quadTo(i + waveLen / 4, 600, i + waveLen / 2, originY);
//path.quadTo(i + waveLen * 3 / 4, 200, i + waveLen, originY);
//canvas.drawPath(path, paint);
//使用rQuadTo,相对位置
path.rQuadTo(waveLen / 4, 200, waveLen / 2, 0);
path.rQuadTo(waveLen / 4, -200, waveLen / 2, 0);
}
canvas.drawPath(path, paint);
实现效果如下
最后封闭空间1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16int waveLen = 200;
int originY = 400;
path.moveTo(-waveLen, originY);
for (int i = -waveLen; i < getWidth() + waveLen; i += waveLen) {
//使用quadTo,绝对位置
//path.quadTo(i + waveLen / 4, 600, i + waveLen / 2, originY);
//path.quadTo(i + waveLen * 3 / 4, 200, i + waveLen, originY);
//canvas.drawPath(path, paint);
//使用rQuadTo,相对位置
path.rQuadTo(waveLen / 4, 200, waveLen / 2, 0);
path.rQuadTo(waveLen / 4, -200, waveLen / 2, 0);
}
path.lineTo(getWidth(), getHeight());
path.lineTo(0, getHeight());
path.close();
canvas.drawPath(path, paint);
得到图像如下
实现将波纹动起来这里贴出整个自定义View代码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
57public class WaveView extends View {
private static final String TAG = "cj5785";
private Path path;
private Paint paint;
private int waveLen = 200;
private int dx;
public WaveView(Context context) {
super(context);
init();
}
private void init() {
path = new Path();
paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(8);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//波形
int originY = 400;
path.reset();
path.moveTo(-waveLen + dx, originY);
for (int i = -waveLen; i < getWidth() + waveLen; i += waveLen) {
//使用quadTo
// path.quadTo(i + waveLen / 4, 600, i + waveLen / 2, originY);
// path.quadTo(i + waveLen * 3 / 4, 200, i + waveLen, originY);
// canvas.drawPath(path, paint);
//使用rQuadTo
path.rQuadTo(waveLen / 4, 200, waveLen / 2, 0);
path.rQuadTo(waveLen / 4, -200, waveLen / 2, 0);
}
//封闭空间
path.lineTo(getWidth(), getHeight());
path.lineTo(0, getHeight());
path.close();
canvas.drawPath(path, paint);
}
public void startAnimation() {
ValueAnimator animator = ValueAnimator.ofInt(0, waveLen);
animator.setDuration(1000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
dx = (int)animation.getAnimatedValue();
postInvalidate();
}
});
animator.start();
}
}
PathMeasure
顾名思义,PathMeasure是一个用来测量Path的类,主要有以下方法
构造方法
方法名 | 释义 |
PathMeasure() | 创建一个空的PathMeasure |
PathMeasure(Path path, boolean forceClosed) | 创建PathMeasure 并关联一个指定的Path(Path需要已经创建完成) |
无参构造函数:
PathMeasure ()
用这个构造函数可创建一个空的PathMeasure
,但是使用之前需要先调用setPath
方法来与Path
进行关联。被关联的Path
必须是已经创建好的,如果关联之后Path
内容进行了更改,则需要使用setPath
方法重新关联有参构造函数:
PathMeasure (Path path, boolean forceClosed)
用这个构造函数是创建一个PathMeasure
并关联一个Path
, 其实和创建一个空的PathMeasure
后调用setPath
进行关联效果是一样的,同样,被关联的Path
也必须是已经创建好的,如果关联之后Path内容进行了更改,则需要使用setPath
方法重新关联
该方法有两个参数,第一个参数自然就是被关联的Path
了,第二个参数是用来确保Path
闭合,如果设置为true
,则不论之前Path
是否闭合,都会自动闭合该Path
(如果Path
可以闭合的话)在这里有两点需要明确
- 不论
forceClosed
设置为何种状态(true
或者false
), 都不会影响原有Path
的状态,即Path
与PathMeasure
关联之后,之前的的Path
不会有任何改变 forceClosed
的设置状态可能会影响测量结果,如果Path
未闭合但在与PathMeasure
关联的时候设置forceClosed
为true
时,测量结果可能会比Path
实际长度稍长一点,获取到到是该Path
闭合时的状态
- 不论
公共方法
返回值 | 方法名 | 释义 |
void | setPath(Path path, boolean forceClosed) | 关联一个Path |
boolean | isClosed() | 是否闭合 |
float | getLength() | 获取Path的长度 |
boolean | nextContour() | 跳转到下一个轮廓 |
boolean | getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) | 截取片段 |
boolean | getPosTan(float distance, float[] pos, float[] tan) | 获取指定长度的位置坐标及该点切线值 |
boolean | getMatrix(float distance, Matrix matrix, int flags) | 获取指定长度的位置坐标及该点 |
setPath
是PathMeasure
与Path
关联的重要方法,效果和构造函数中两个参数的作用是一样的isClosed
用于判断Path
是否闭合,但是如果你在关联Path
的时候设置forceClosed
为true
的话,这个方法的返回值则一定为true
getLength
用于获取Path
的总长度getSegment
用于获取Path
的一个片段参数 作用 备注 返回值(boolean)
判断截取是否成功 true
表示截取成功,结果存入dst
中,false
截取失败,不会改变dst
中内容startD
开始截取位置距离 Path
起点的长度取值范围: 0 <= startD < stopD <= Path
总长度stopD
结束截取位置距离 Path
起点的长度取值范围: 0 <= startD < stopD <= Path
总长度dst
截取的 Path 将会添加到 dst
中注意: 是添加,而不是替换 startWithMoveTo
起始点是否使用 moveTo
用于保证截取的 Path
第一个点位置不变
1. 如果startD
、stopD
的数值不在取值范围[0, getLength]
内,或者startD == stopD
则返回值为false
,不会改变dst
内容
2. 如果在安卓4.4或者之前的版本,在默认开启硬件加速的情况下,更改dst
的内容后可能绘制会出现问题,请关闭硬件加速或者给dst
添加一个单个操作,例如:dst.rLineTo(0, 0)
3. 可以用以下规则来判断startWithMoveTo
的取值
取值 主要功用 true
保证截取得到的 Path
片段不会发生形变false
保证存储截取片段的 Path(dst)
的连续性nextContour
我们知道Path
可以由多条曲线构成,但不论是getLength
,getgetSegment
或者是其它方法,都只会在其中第一条线段上运行,而这个nextContour
就是用于跳转到下一条曲线到方法,如果跳转成功,则返回true
,如果跳转失败,则返回false
getPosTan
这个方法是用于得到路径上某一长度的位置以及该位置的正切值:参数 作用 备注 返回值(boolean)
判断获取是否成功 true
表示成功,数据会存入pos
和tan
中,false
表示失败,pos
和tan
不会改变distance
距离 Path
起点的长度取值范围: 0 <= distance <= getLength
pos
该点的坐标值 坐标值: (x==[0], y==[1])
tan
该点的正切值 正切值: (x==[0], y==[1])
getMatrix
这个方法是用于得到路径上某一长度的位置以及该位置的正切值的矩阵参数 作用 备注 返回值(boolean)
判断获取是否成功 true
表示成功,数据会存入matrix
中,false
失败,matrix
内容不会改变distance
距离 Path
起点的长度取值范围: 0 <= distance <= getLength
matrix
根据 falgs
封装好的matrix
会根据 flags
的设置而存入不同的内容flags
规定哪些内容会存入到 matrix
中可选择 POSITION_MATRIX_FLAG(位置)
ANGENT_MATRIX_FLAG(正切)
使用示例
- 构造方法
1
2
3
4
5
6
7
8
9path.reset();
path.lineTo(0, 400);
path.lineTo(400, 400);
path.lineTo(400, 0);
PathMeasure measure1 = new PathMeasure(path, false);
PathMeasure measure2 = new PathMeasure(path, true);
Log.d(TAG, "onDraw: measure1=" + measure1.getLength());
Log.d(TAG, "onDraw: measure2=" + measure2.getLength());
canvas.drawPath(path, paint);
打印1
2D/cj578: onDraw: measure1=1200.0
D/cj578: onDraw: measure2=1600.0
getLength()
与nextContour()
多路径需要关闭硬件加速1
2
3
4
5
6
7
8
9
10
11
12
13
14path.reset();
path.addRect(-300, -300, 300, 300, Path.Direction.CCW);
path.addRect(-200, -200, 200, 200, Path.Direction.CCW);
path.addRect(-100, -100, 100, 100, Path.Direction.CCW);
canvas.drawPath(path, paint);
PathMeasure measure = new PathMeasure(path, false);
float len1 = measure.getLength();
Log.d(TAG, "onDraw: len1=" + len1);
int i = 2;
while (measure.nextContour()) {
float len = measure.getLength();
Log.d(TAG, "onDraw: len" + i + "=" + len);
i++;
}
打印1
2
3D/cj578: onDraw: len1=2400.0
D/cj578: onDraw: len2=1600.0
D/cj578: onDraw: len3=800.0
getSegment()
截取片断1
2
3
4
5
6path.reset();
path.addRect(-300, -300, 300, 300, Path.Direction.CCW);
PathMeasure measure = new PathMeasure(path, false);
Path dst = new Path();
measure.getSegment(0, 1600, dst, true);
canvas.drawPath(dst, paint);getPosTan()
获取位置和正切1
2
3
4
5
6
7
8
9path.reset();
path.addCircle(0, 0, 300, Path.Direction.CW);
PathMeasure measure = new PathMeasure(path, false);
float[] pos = new float[2];
float[] tan = new float[2];
measure.getPosTan(measure.getLength() / 4, pos, tan);
Log.d(TAG, "onDraw: pos-x:" + pos[0] + ",pos-y:" + pos[1]);
Log.d(TAG, "onDraw: tan-x:" + tan[0] + ",tan-y:" + tan[1]);
canvas.drawPath(path, paint);
打印1
2D/cj578: onDraw: pos-x:4.2605415E-4,pos-y:300.0
D/cj578: onDraw: tan-x:-1.0,tan-y:1.4448632E-6