PathMeasure学习

概述

顾名思义,PathMeasure是一个用来测量Path的类,主要有以下方法:

构造方法

公共方法

方法详解
构造函数

无参构造函数:

1
PathMeasure ()

用这个构造函数可创建一个空的 PathMeasure,但是使用之前需要先调用 setPath 方法来与 Path 进行关联。被关联的 Path 必须是已经创建好的,如果关联之后 Path 内容进行了更改,则需要使用 setPath 方法重新关联。

有参构造函数:

1
PathMeasure (Path path, boolean forceClosed)

用这个构造函数是创建一个 PathMeasure 并关联一个 Path, 其实和创建一个空的 PathMeasure 后调用 setPath 进行关联效果是一样的,同样,被关联的 Path 也必须是已经创建好的,如果关联之后 Path 内容进行了更改,则需要使用 setPath 方法重新关联。

该方法有两个参数,第一个参数自然就是被关联的 Path 了,第二个参数是用来确保 Path 闭合,如果设置为 true, 则不论之前Path是否闭合,都会自动闭合该 Path(如果Path可以闭合的话)。

在这里有两点需要明确:

    1. 不论 forceClosed 设置为何种状态(true 或者 false), 都不会影响原有Path的状态,即 Path 与 PathMeasure 关联之后,之前的的 Path 不会有任何改变。
    1. forceClosed 的设置状态可能会影响测量结果,如果 Path 未闭合但在与 PathMeasure 关联的时候设置 forceClosed 为 true 时,测量结果可能会比 Path 实际长度稍长一点,获取到到是该 Path 闭合时的状态。
setPath、 isClosed 和 getLength

setPath 是 PathMeasure 与 Path 关联的重要方法,效果和 构造函数 中两个参数的作用是一样的。

isClosed 用于判断 Path 是否闭合,但是如果你在关联 Path 的时候设置 forceClosed 为 true 的话,这个方法的返回值则一定为true。

getLength 用于获取 Path 的总长度。

getSegment

getSegment 用于获取Path的一个片段,方法如下:

1
boolean getSegment (float startD, float stopD, Path dst, boolean startWithMoveTo)

方法各个参数释义:

  • 如果 startD、stopD 的数值不在取值范围 [0, getLength] 内,或者 startD == stopD 则返回值为 false,不会改变 dst 内容。
  • 如果在安卓4.4或者之前的版本,在默认开启硬件加速的情况下,更改 dst 的内容后可能绘制会出现问题,请关闭硬件加速或者给 dst 添加一个单个操作,例如: dst.rLineTo(0, 0)
示意Demo1:

我们创建了一个 Path, 并在其中添加了一个矩形,现在我们想截取矩形中的一部分,就是下图中红色的部分。

矩形边长400dp,起始点在左上角,顺时针

1
2
3
4
5
6
7
8
9
10
11
12
13
canvas.translate(mViewWidth / 2, mViewHeight / 2);          // 平移坐标系

Path path = new Path(); // 创建Path并添加了一个矩形,设置绘制方向为顺时针
path.addRect(-200, -200, 200, 200, Path.Direction.CW);

Path dst = new Path(); // 创建用于存储截取后内容的 Path,此时path中并没有内容

PathMeasure measure = new PathMeasure(path, false); // 将 Path 与 PathMeasure 关联

// 截取一部分存入dst中,并使用 moveTo 保持截取得到的 Path 第一个点的位置不变
measure.getSegment(200, 600, dst, true);

canvas.drawPath(dst, mDeafultPaint); // 绘制 dst

结果如下:

从上图可以看到我们成功到将需要到片段截取了出来,然而当 dst 中有内容时会怎样呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
canvas.translate(mViewWidth / 2, mViewHeight / 2);          // 平移坐标系

Path path = new Path(); // 创建Path并添加了一个矩形
path.addRect(-200, -200, 200, 200, Path.Direction.CW);

Path dst = new Path(); // 创建用于存储截取后内容的 Path
dst.lineTo(-300, -300); // <--- 在 dst 中添加一条线段

PathMeasure measure = new PathMeasure(path, false); // 将 Path 与 PathMeasure 关联

measure.getSegment(200, 600, dst, true); // 截取一部分 并使用 moveTo 保持截取得到的 Path 第一个点的位置不变

canvas.drawPath(dst, mDeafultPaint); // 绘制 Path

结果如下:

从上面的示例可以看到 dst 中的线段保留了下来,可以得到结论:被截取的 Path 片段会添加到 dst 中,而不是替换 dst 中到内容。

前面两个例子中 startWithMoveTo 均为 true, 如果设置为false会怎样呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
canvas.translate(mViewWidth / 2, mViewHeight / 2);          // 平移坐标系

Path path = new Path(); // 创建Path并添加了一个矩形
path.addRect(-200, -200, 200, 200, Path.Direction.CW);

Path dst = new Path(); // 创建用于存储截取后内容的 Path
dst.lineTo(-300, -300); // 在 dst 中添加一条线段

PathMeasure measure = new PathMeasure(path, false); // 将 Path 与 PathMeasure 关联

measure.getSegment(200, 600, dst, false); // <--- 截取一部分 不使用 startMoveTo, 保持 dst 的连续性

canvas.drawPath(dst, mDeafultPaint); // 绘制 Path

结果如下:

从该示例我们又可以得到一条结论:如果 startWithMoveTo 为 true, 则被截取出来到Path片段保持原状,如果 startWithMoveTo 为 false,则会将截取出来的 Path 片段的起始点移动到 dst 的最后一个点,以保证 dst 的连续性。

从而我们可以用以下规则来判断 startWithMoveTo 的取值:

nextContour

我们知道 Path 可以由多条曲线构成,但不论是 getLength , getSegment 或者是其它方法,都只会在其中第一条线段上运行,而这个 nextContour 就是用于跳转到下一条曲线到方法,如果跳转成功,则返回 true, 如果跳转失败,则返回 false。

示意Demo2:

如下,我们创建了一个 Path 并使其中包含了两个闭合的曲线,内部的边长是200,外面的边长是400,现在我们使用 PathMeasure 分别测量两条曲线的总长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
canvas.translate(mViewWidth / 2, mViewHeight / 2);      // 平移坐标系

Path path = new Path();

path.addRect(-100, -100, 100, 100, Path.Direction.CW); // 添加小矩形 周长800
path.addRect(-200, -200, 200, 200, Path.Direction.CW); // 添加大矩形 周长1600

canvas.drawPath(path,mDeafultPaint); // 绘制 Path

PathMeasure measure = new PathMeasure(path, false); // 将Path与PathMeasure关联

float len1 = measure.getLength(); // 获得第一条路径的长度

measure.nextContour(); // 跳转到下一条路径

float len2 = measure.getLength(); // 获得第二条路径的长度

Log.i("LEN","len1="+len1); // 输出两条路径的长度
Log.i("LEN","len2="+len2);

log输出结果:

1
2
com.gcssloop.canvas I/LEN: len1=800.0
com.gcssloop.canvas I/LEN: len2=1600.0

通过测试,我们可以得到以下内容:

  • 1.曲线的顺序与 Path 中添加的顺序有关。
  • 2.getLength 获取到到是当前一条曲线分长度,而不是整个 Path 的长度。
  • 3.getLength 等方法是针对当前的曲线。
getPosTan

这个方法是用于得到路径上某一长度的位置以及该位置的正切值

1
boolean getPosTan (float distance, float[] pos, float[] tan)

参数释义:

使用 Math.atan2(tan[1], tan[0]) 将 tan 转化为角(单位为弧度)的时候要注意参数顺序。

1
2
3
4
5
// 省略部分代码
tan = new float[2];
PathMeasure measure = new PathMeasure(path, false); // 创建 PathMeasure
measure.getPosTan(measure.getLength() * currentValue, pos, tan); // 获取当前位置的坐标以及趋势
float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI); // 计算图片旋转角度
getMatrix

这个方法是用于得到路径上某一长度的位置以及该位置的正切值的矩阵

boolean getMatrix (float distance, Matrix matrix, int flags)

方法参数含义:

1
2
3
4
5
//示意操作
// 获取当前位置的坐标以及趋势的矩阵
measure.getMatrix(measure.getLength() * currentValue, mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);

mMatrix.preTranslate(-mBitmap.getWidth() / 2, -mBitmap.getHeight() / 2);
Path & SVG

我们知道,用Path可以创建出各种个样的图形,但如果图形过于复杂时,用代码写就不现实了,不仅麻烦,而且容易出错,所以在绘制复杂的图形时我们一般是将 SVG 图像转换为 Path

什么是SVG?

SVG 是一种矢量图,内部用的是 xml 格式化存储方式存储这操作和数据,你完全可以将 SVG 看作是 Path 的各项操作简化书写后的存储格式。

Path 和 SVG 结合通常能诞生出一些奇妙的东西,具体去查看其它文章。

0%