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工具类
Path path = new Path();
二阶贝塞尔:其参数第一对是控制点,第二对是结束点
path.quadTo();
e.g.
path.moveTo(100, 400);
path.quadTo(200, 0, 500, 400);
path.quadTo(700, 600, 900, 400);
canvas.drawPath(path, paint);
三阶贝塞尔
path.cubicTo();
e.g.
path.moveTo(100, 1000);
path.cubicTo(300, 900, 600, 1200, 900, 1000);
canvas.drawPath(path, paint);
生成二阶及三阶贝塞尔曲线如下图
贝塞尔曲线实现波形图
在onDraw()
里面绘制
int 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);
实现效果如下
最后封闭空间
int 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代码
public 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);
}
@Override
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() {
@Override
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`第一个点位置不变 |
取值 | 主要功用 |
`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(正切)` |
使用示例
- 构造方法
path.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);
打印
D/cj578: onDraw: measure1=1200.0
D/cj578: onDraw: measure2=1600.0
getLength()
与nextContour()
多路径需要关闭硬件加速
path.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++;
}
打印
D/cj578: onDraw: len1=2400.0
D/cj578: onDraw: len2=1600.0
D/cj578: onDraw: len3=800.0
getSegment()
截取片断
path.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()
获取位置和正切
path.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);
打印
D/cj578: onDraw: pos-x:4.2605415E-4,pos-y:300.0
D/cj578: onDraw: tan-x:-1.0,tan-y:1.4448632E-6