本篇内容基本都来自参考文献,有兴趣的可自行阅读查看

  • 一基本操作

    • 1 moveTo setLastPoint lineTo 和 close
    • 2 addXxx与arcTo
    • 3 路径方向PathDirection
    • 4 isEmpty isRectisConvex set 和 offset
    • 5 rXxx方法
    • 6 computeBounds 计算边界
    • 7 填充模式FillType
    • 8 布尔运算API19
    • 9 重置路径
  • 二贝塞尔曲线
    • 1 二阶曲线
    • 2 三阶曲线
    • 3 降阶与升阶
  • 三PathMeasure路径测量
    • 1 构建
    • 2 setPath isClosed 和 getLength
    • 3 getSegment获取路径片段
    • 4 nextContour跳到下一线段
    • 5 getPosTan当前路径上某点的正切值
    • 6 getMatrix当前路径上某点的正切值矩阵
  • 四Path SVG
  • 参考文献

请关闭硬件加速,以免引起不必要的问题!
请关闭硬件加速,以免引起不必要的问题!
请关闭硬件加速,以免引起不必要的问题!

在AndroidMenifest文件中application节点下添上 android:hardwareAccelerated=”false”以关闭整个应用的硬件加速。
更多请参考这里:Android的硬件加速及可能导致的问题

本次特地开了一篇详细讲解Path,为什么要单独摘出来呢,这是因为Path在2D绘图中是一个很重要的东西。

在前面我们讲解的所有绘制都是简单图形(如 矩形 圆 圆弧等),而对于那些复杂一点的图形则没法去绘制(如绘制一个心形 正多边形 五角星等),而使用Path不仅能够绘制简单图形,也可以绘制这些比较复杂的图形,另外,根据路径绘制文本和剪裁画布都会用到Path

  • Path封装了由直线和曲线(二次,三次贝塞尔曲线)构成的几何路径。你能用Canvas中的drawPath来把这条路径画出来(同样支持Paint的不同绘制模式)
  • 可以用于剪裁画布
  • 可以用根据路径绘制文字
  • 我们有时会用Path来描述一个图像的轮廓,所以也会称为轮廓线(轮廓线仅是Path的一种使用方法,两者并不等价)

以下方法中:xxxto是真的画出来一条线(moveTo除外),而addxxx仅是在路径中加入某个图形并不绘制出来

作用 相关方法 备注
移动起点 moveTo 移动下一次操作的起点位置
设置终点 setLastPoint 重置当前path中最后一个点位置,如果在绘制之前调用,效果和moveTo相同
连接直线 lineTo 添加上一个点到当前点之间的直线到Path
闭合路径 close 连接第一个点连接到最后一个点,形成一个闭合区域
添加内容 addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo 添加(矩形, 圆角矩形, 椭圆, 圆, 路径, 圆弧) 到当前Path (注意addArc和arcTo的区别)
是否为空 isEmpty 判断Path是否为空
是否为矩形 isRect 判断path是否是一个矩形
替换路径 set 用新的路径替换到当前路径所有内容
偏移路径 offset 对当前路径之前的操作进行偏移(不会影响之后的操作)
贝塞尔曲线 quadTo, cubicTo 分别为二次和三次贝塞尔曲线的方法
rXxx方法 rMoveTo, rLineTo, rQuadTo, rCubicTo 不带r的方法是基于原点的坐标系(偏移量), rXxx方法是基于当前点坐标系(偏移量)
填充模式 setFillType, getFillType, isInverseFillType, toggleInverseFillType 设置,获取,判断和切换填充模式
提示方法 incReserve 提示Path还有多少个点等待加入(这个方法貌似会让Path优化存储结构)
布尔操作(API19) op 对两个Path进行布尔运算(即取交集、并集等操作)
计算边界 computeBounds 计算Path的边界
重置路径 reset, rewind 清除Path中的内容reset不保留内部数据结构,但会保留FillType.rewind会保留内部的数据结构,但不保留FillType
矩阵操作 transform 矩阵变换

一、基本操作

1.1 moveTo、 setLastPoint、 lineTo 和 close

moveTo、 setLastPoint、 close都无法直接看到效果,借助有具现化效果的lineTo才能让这些方法现出原形

//向目标点处直线绘制:默认点就是坐标原点O
public void lineTo (float x, float y)// 移动下一次操作的起点位置:不影响之前的,只影响之后的操作
public void moveTo (float x, float y)// 设置之前操作的最后一个点位置:会影响之前的,也会影响之后的操作
public void setLastPoint (float dx, float dy)//闭合路径:用于连接当前最后一个点和最初的一个点(如果两个点不重合的话),最终形成一个封闭的图形
//close的作用是封闭路径,与连接当前最后一个点和第一个点并不等价。如果连接了最后一个点和第一个点仍然无法形成封闭图形,则close什么 也不做
public void close ()

我们还是直接看示例吧:

//1 moveTo
canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
Path path = new Path();                     // 创建Path
path.lineTo(200, 200);                      // lineTo
path.moveTo(200,100);                       // moveTo
path.lineTo(200,0);                         // lineTo
canvas.drawPath(path, mPaint);              // 绘制Path

moveTo只改变下次操作的起点,在执行完第一次LineTo的时候,本来的默认点位置是A(200,200),但是moveTo将其改变成为了C(200,100),所以在第二次调用lineTo的时候就是连接C(200,100) 到 B(200,0) 之间的直线(用蓝色圈2标注)

//2
canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
Path path = new Path();                     // 创建Path
path.lineTo(200, 200);                      // lineTo
path.setLastPoint(200,100);                 // setLastPoint
path.lineTo(200,0);                         // lineTo
canvas.drawPath(path, mPaint);              // 绘制Path

setLastPoint是重置上一次操作的最后一个点,在执行完第一次的lineTo的时候,最后一个点是A(200,200),而setLastPoint更改最后一个点为C(200,100),所以在实际执行的时候,第一次的lineTo就不是从原点O到A(200,200)的连线了,而变成了从原点O到C(200,100)之间的连线了。

在执行完第一次lineTo和setLastPoint后,最后一个点的位置是C(200,100),所以在第二次调用lineTo的时候就是C(200,100) 到 B(200,0) 之间的连线(用蓝色圈2标注)

//3
canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
Path path = new Path();                     // 创建Path
path.lineTo(200, 200);                      // lineTo
path.lineTo(200,0);                         // lineTo
path.close();                               // close
canvas.drawPath(path, mPaint);              // 绘制Path

很明显,两个lineTo分别代表第1和第2条线,而close在此处的作用就算连接了B(200,0)点和原点O之间的第3条线,使之形成一个封闭的图形。

1.2 addXxx与arcTo

addXXX方法只是在路径中加入,只有在调用canvas.drawPath(path,mPaint)后才能显现

// 第一类(基本形状)
//这一类就是在path中添加一个基本形状,基本形状部分和前面所讲的绘制基本形状并无太大差别,详情参考<(4.1.36)android Graphics 图形学解析>
// 圆形
public void addCircle (float x, float y, float radius, Path.Direction dir)
// 椭圆
public void addOval (RectF oval, Path.Direction dir)
// 矩形
public void addRect (float left, float top, float right, float bottom, Path.Direction dir)
public void addRect (RectF rect, Path.Direction dir)
// 圆角矩形
public void addRoundRect (RectF rect, float[] radii, Path.Direction dir)
public void addRoundRect (RectF rect, float rx, float ry, Path.Direction dir)
// 圆弧
//oval  圆弧的外切矩形。
//startAngle    开始角度
//sweepAngle    扫过角度。取值范围是 [-360, 360),不包括360,当 >= 360 或者 < -360 时将不会绘制任何内容, 对于360,你可以用一个接近的值替代,例如: 359.99
public void addArc (RectF oval, float startAngle, float sweepAngle)// 第二类(Path),将两个Path合并成为一个
public void addPath (Path src)
//将src进行了位移之后再添加进当前path中,具体见下文示例
public void addPath (Path src, float dx, float dy)
public void addPath (Path src, Matrix matrix)// 第三类(arcTo)
// 添加一个圆弧到path:添加一个圆弧到path,如果圆弧的起点和上次最后一个坐标点不相同,就连接两个点
public void arcTo (RectF oval, float startAngle, float sweepAngle)
//forceMoveTo: true 将最后一个点移动到圆弧起点,即不连接最后一个点与圆弧起点。 等价于 addArc
//forceMoveTo:false 不移动,而是连接最后一个点与圆弧起点。 等价于arcTo
public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
  • addPath (Path src, float dx, float dy)示例:

首先我们新建地方两个Path(矩形和圆形)中心都是坐标原点,我们在将包含圆形的path添加到包含矩形的path之前将其进行移动了一段距离,最终绘制出来的效果就如图所示

canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
canvas.scale(1,-1);                         // <-- 注意 翻转y坐标轴Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CW);Path src = new Path();
src.addCircle(0,0,100, Path.Direction.CW);
path.addPath(src,0,200);mPaint.setColor(Color.BLACK);           // 绘制合并后的路径
canvas.drawPath(path,mPaint);

  • addArc 与 arcto
anvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
canvas.scale(1,-1);                         // <-- 注意 翻转y坐标轴
Path path = new Path();
path.lineTo(100,100);
RectF oval = new RectF(0,0,300,300);//1
path.addArc(oval,0,270);
// path.arcTo(oval,0,270,true);             // <-- 和上面一句作用等价//2
path.arcTo(oval,0,270);
// path.arcTo(oval,0,270,false);             // <-- 和上面一句作用等价canvas.drawPath(path,mPaint);

1.3 路径方向Path.Direction

Path.Direction用于指明路径的方向,在添加图形时确定闭合顺序(各个点的记录顺序),对图形的渲染结果有影响(是判断图形渲染的重要条件)
- CW clockwise 顺时针
- CCW counter-clockwise 逆时针

具体Path的影响还是看示例:

canvas.translate(mWidth / 2, mHeight / 2);  // 移动坐标系到屏幕中心
Path path = new Path();
//path.addRect(-200,-200,200,200, Path.Direction.CW);
path.addRect(-200,-200,200,200, Path.Direction.CCW);
path.setLastPoint(-300,300);                // <-- 重置最后一个点的位置
canvas.drawPath(path,mPaint);

绘制一个矩形(仅绘制边线),实际上只需要进行四次lineTo操作就行了,也就是说,只需要知道4个点的坐标,然后使用moveTo到第一个点,之后依次lineTo就行了(从上面的测试可以看出,在实际绘制中也确实是这么干的)

  • 我们采用的是顺时针(CW),所以记录的点的顺序就是 A -> B -> C -> D. 最后一个点就是D,我们这里使用setLastPoint改变最后一个点的位置实际上是改变了D的位置。
  • 如果我们将顺时针改为逆时针(CCW),则记录点的顺序应该就是 A -> D -> C -> B, 再使用setLastPoint则改变的是B的位置

1.4 isEmpty、 isRect、isConvex、 set 和 offset

//判断path中是否包含内容
public boolean isEmpty ()//判断path是否是一个矩形,如果是一个矩形的话,会将矩形的信息存放进参数rect中
public boolean isRect (RectF rect)//将新的path赋值到现有path,大致相当于 path = src;
public void set (Path src)//对path进行一段平移,只作用于当前path
public void offset (float dx, float dy)
// dst不为空   将当前path平移后的状态存入dst中,不会影响当前path
// dat为空(null)  平移将作用于当前path,相当于第一种方法
public void offset (float dx, float dy, Path dst)

1.5 rXxx方法

rMoveTo(float dx, float dy)rLineTo(float dx, float dy)rQuadTo(float dx1, float dy1, float dx2, float dy2) rCubicTo(float x1, float y1, float x2, float y2,float x3, float y3)

rXxx此类方法可以看到和前面的一些方法看起来很像,只是在前面多了一个r,这个r的在于标示:rXxx方法的坐标使用的是相对位置(基于当前点的位移),而之前方法的坐标是绝对位置(基于当前坐标系的坐标)

譬如:

//1
//在这个例子中,先移动点到坐标(100,100)处,之后再连接 点(100,100) 到 (100,200) 之间点直线,非常简单,画出来就是一条竖直的线
Path path = new Path();
path.moveTo(100,100);
path.lineTo(100,200);
canvas.drawPath(path,mDeafultPaint);//2
//在屏幕上原本是竖直的线变成了倾斜的线。这是因为最终我们连接的是 (100,100) 和 (200, 300) 之间的线段
//在使用rLineTo之前,当前点的位置在 (100,100) , 使用了 rLineTo(100,200) 之后,下一个点的位置是在当前点的基础上加上偏移量得到的,即 (100+100, 100+200) 这个位置
Path path = new Path();
path.moveTo(100,100);
path.rLineTo(100,200);
canvas.drawPath(path,mDeafultPaint);

1.6 computeBounds 计算边界

void computeBounds (RectF bounds, boolean exact)
  • 这个方法主要作用是计算Path所占用的空间以及所在位置

    • bounds 测量结果会放入这个矩形
    • exact 是否精确测量,目前这一个参数作用已经废弃,一般写true即可。

1.7 填充模式FillType

//设置填充规则
setFillType
//获取当前填充规则
getFillType
//判断是否是反向(INVERSE)规则
isInverseFillType
//切换填充规则(即原有规则与反向规则之间相互切换)
toggleInverseFillType   

我们在之前的文章中了解到,Paint有三种样式,“描边” “填充” 以及 “描边加填充”,我们这里所了解到就是在Paint设置为后两种样式时不同的填充模式对图形渲染效果的影响

我们要给一个图形内部填充颜色,首先需要分清哪一部分是外部,哪一部分是内部,机器不像我们人那么聪明,机器是如何判断内外呢?

模式 简介
EVEN_ODD 奇偶规则
INVERSE_EVEN_ODD 反奇偶规则
WINDING 非零环绕数规则
INVERSE_WINDING 反非零环绕数规则

PS:此处所有的图形均为封闭图形,不包括图形不封闭这种情况

  • 奇偶规则: 奇数表示在图形内,偶数表示在图形外

    • 从任意位置p作一条射线, 若与该射线相交的图形边的数目为奇数,则p是图形内部点,否则是外部点。
  • 非零环绕数规则: 若环绕数为0表示在图形外,非零表示在图形内

    • 先使图形的边变为矢量。将环绕数初始化为零。再从任意位置p作一条射线。当从p点沿射线方向移动时,对在每个方向上穿过射线的边计数,每当图形的边从右到左穿过射线时,环绕数加1,从左到右时,环绕数减1。处理完图形的所有相关边之后,若环绕数为非零,则p为内部点,否则,p是外部点

  • 奇偶规则

    • P1: 从P1发出一条射线,发现图形与该射线相交边数为0,偶数,故P1点在图形外部。
    • P2: 从P2发出一条射线,发现图形与该射线相交边数为1,奇数,故P2点在图形内部。
    • P3: 从P3发出一条射线,发现图形与该射线相交边数为2,偶数,故P3点在图形外部。
  • 非零环绕数规则
    • 从P1点发出一条射线,沿射线防线移动,并没有与边相交点部分,环绕数为0,故P1在图形外边。
    • P2: 从P2点发出一条射线,沿射线方向移动,与图形点左侧边相交,该边从左到右穿过穿过射线,环绕数-1,最终环绕数为-1,故P2在图形内部。
    • P3: 从P3点发出一条射线,沿射线方向移动,在第一个交点处,底边从右到左穿过射线,环绕数+1,在第二个交点处,右侧边从左到右穿过射线,环绕数-1,最终环绕数为0,故P3在图形外部

通常,这两种方法的判断结果是相同的,但也存在两种方法判断结果不同的情况,如下面这种情况:

1.8 布尔运算(API19)

布尔操作与我们中学所学的集合操作非常像,只要知道集合操作中等交集,并集,差集等操作,那么理解布尔操作也是很容易的

//对 调用path 和 path 执行布尔运算,运算方式由第二个参数指定,运算结果存入到调用path中。
boolean op (Path path, Path.Op op)
// 对 path1 和 path2 执行布尔运算,运算方式由第三个参数指定,运算结果存入到 调用path 中。
boolean op (Path path1, Path path2, Path.Op op)

布尔操作是两个Path之间的运算,主要作用是用一些简单的图形通过一些规则合成一些相对比较复杂,或难以直接得到的图形。

如太极中的阴阳鱼,如果用贝塞尔曲线制作的话,可能需要六段贝塞尔曲线才行,而在这里我们可以用四个Path通过布尔运算得到,而且会相对来说更容易理解一点

逻辑名称 类比 说明 示意图
DIFFERENCE 差集 Path1中减去Path2后剩下的部分
REVERSE_DIFFERENCE 差集 Path2中减去Path1后剩下的部分
INTERSECT 交集 Path1与Path2相交的部分
UNION 并集 包含全部Path1和Path2
XOR 异或 包含Path1与Path2但不包括两者相交的部分

我们在这里给出一个阴阳鱼效果:

canvas.translate(mViewWidth / 2, mViewHeight / 2);Path path1 = new Path();
Path path2 = new Path();
Path path3 = new Path();
Path path4 = new Path();path1.addCircle(0, 0, 200, Path.Direction.CW);
path2.addRect(0, -200, 200, 200, Path.Direction.CW);
path3.addCircle(0, -100, 100, Path.Direction.CW);
path4.addCircle(0, 100, 100, Path.Direction.CCW);path1.op(path2, Path.Op.DIFFERENCE);
path1.op(path3, Path.Op.UNION);
path1.op(path4, Path.Op.DIFFERENCE);canvas.drawPath(path1, mDeafultPaint);

1.9 重置路径

这个两个方法应该何时选择呢?

选择权重: FillType > 数据结构。因为“FillType”影响的是显示效果,而“数据结构”影响的是重建速度

重置Path有两个方法,分别是reset和rewind,两者区别主要有一下两点:

方法 是否保留FillType设置 是否保留原有数据结构
reset
rewind

二、贝塞尔曲线

类型 作用
数据点 确定曲线的起始和结束位置
控制点 确定曲线的弯曲程度

贝塞尔曲线是用一系列点来控制曲线状态的:

  • 一阶曲线是没有控制点的,仅有两个数据点(A 和 B),最终效果一个线段

2.1 二阶曲线

  • 二阶曲线由两个数据点(A 和 C),一个控制点(B)来描述曲线状态

    • 连接AB BC,并在AB上取点D,BC上取点E,使其满足条件:AD/AB = BE/BC
    • 连接DE,取点F,使得:AD/AB = BE/BC = DF/DE。 此时F点为目标点
//x1,y1控制点
//x2,y2终点quadTo(float x1, float y1, float x2, float y2)

2.2 三阶曲线

  • 三阶曲线由两个数据点(A 和 D),两个控制点(B 和 C)来描述曲线状态

//x1,y1控制点A
//x2,y2控制点B
//x3,y3终点
cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)

2.3 降阶与升阶

三阶曲线相比于二阶曲线可以制作更加复杂的形状,但是对于高阶的曲线,用低阶的曲线组合也可达到相同的效果,就是传说中的降阶。因此我们对贝塞尔曲线的封装方法一般最高只到三阶曲线

类型 释义 变化
降阶 在保持曲线形状与方向不变的情况下,减少控制点数量,即降低曲线阶数 方法变得简单,数据点变多,控制点可能减少,灵活性变弱
升阶 在保持曲线形状与方向不变的情况下,增加控制点数量,即升高曲线阶数 方法更加复杂,数据点不变,控制点增加,灵活性变强

三、PathMeasure路径测量

Path 可以由多条曲线构成, getLength , getSegment 或者是其它方法,都只会在其中第一条线段上运行

PathMeasure是一个用来测量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) 获取指定长度的位置坐标及该点Matrix

3.1 构建

它构造方法如下:

//创建一个空的PathMeasure
PathMeasure ()
//创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)
PathMeasure(Path path, boolean forceClosed)

其中:

  • 无参构造函数:

    • 用这个构造函数可创建一个空的 PathMeasure,但是使用之前需要先调用 setPath 方法来与 Path 进行关联
    • 被关联的 Path 必须是已经创建好的,如果关联之后 Path 内容进行了更改,则需要使用 setPath 方法重新关联
  • 有参构造函数
    • 创建一个 PathMeasure 并关联一个 Path(其实和创建一个空的 PathMeasure 后调用 setPath 进行关联效果是一样的)
    • 第二个参数forceClosed是用来确保 Path 闭合,如果设置为 true, 则不论之前Path是否闭合,都会自动闭合该 Path(如果Path可以闭合的话)

需要注意的是,在构造函数中:

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

3.2 setPath、 isClosed 和 getLength

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

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

  • getLength 用于获取 Path 的总长度

3.3 getSegment获取路径片段

//沿路径方向获取指定长度的路径片段
boolean getSegment (float startD, float stopD, Path dst, boolean startWithMoveTo)
参数 作用 备注
返回值(boolean) 判断截取是否成功 true 表示截取成功,结果存入dst中,false 截取失败,不会改变dst中内容
startD 开始截取位置距离 Path 起点的长度 取值范围: 0 <= startD < stopD <= Path总长度
stopD 结束截取位置距离 Path 起点的长度 取值范围: 0 <= startD < stopD <= Path总长度
dst 截取的 Path 将会添加到 dst 中 注意: 是添加,而不是替换
startWithMoveTo 被截取路径的起始点是否使用 如果 startWithMoveTo 为 true, 则被截取出来到Path片段保持原状;如果 startWithMoveTo 为 false,则会将截取出来的 Path 片段的起始点移动到 dst 的最后一个点,以保证 dst 的连续性
  • 截取一般都是前闭后开
  • 如果 startD、stopD 的数值不在取值范围 [0, getLength] 内,或者 startD == stopD 则返回值为 false,不会改变 dst 内容。
  • 如果在安卓4.4或者之前的版本,在默认开启硬件加速的情况下,更改 dst 的内容后可能绘制会出现问题,请关闭硬件加速或者给 dst 添加一个单个操作,例如: dst.rLineTo(0, 0)

我们看一个例子:

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 中添加一条线段// 将 Path 与 PathMeasure 关联
PathMeasure measure = new PathMeasure(path, false);
// 截取一部分 并使用 moveTo 保持截取得到的 Path 第一个点的位置不变
measure.getSegment(200, 600, dst, true);
//measure.getSegment(200, 600, dst, false);canvas.drawPath(dst, mDeafultPaint);// 绘制 Path

3.4 nextContour跳到下一线段

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

  • 曲线的顺序与 Path 中添加的顺序有关。
  • getLength 获取到到是当前一条曲线分长度,而不是整个 Path 的长度。
  • getLength 等方法是针对当前的曲线(其它方法请自行验证)
PathMeasure measure = new PathMeasure(path, false);// 将Path与PathMeasure关联
float len1 = measure.getLength();// 获得第一条路径的长度
measure.nextContour();// 跳转到下一条路径
float len2 = measure.getLength(); // 获得第二条路径的长度

3.5 getPosTan当前路径上某点的正切值

boolean getPosTan (float distance, float[] pos, float[] tan)
参数 作用 备注
返回值(boolean) 判断获取是否成功 true表示成功,数据会存入 pos 和 tan 中,false 表示失败,pos 和 tan 不会改变
distance 距离 Path 起点的长度 取值范围: 0 <= distance <= getLength
pos 该点的坐标值 结果:当前点在画布上的位置,有两个数值,分别为x,y坐标。
tan 该点沿路径切线方向与X轴的交点的正切值 结果:当前点在曲线上的方向,使用 Math.atan2(tan[1], tan[0]) 获取到正切角的弧度值。
canvas.translate(mViewWidth / 2, mViewHeight / 2);      // 平移坐标系Path path = new Path();                                 // 创建 Pathpath.addCircle(0, 0, 200, Path.Direction.CW);           // 添加一个圆形PathMeasure measure = new PathMeasure(path, false);     // 创建 PathMeasurecurrentValue += 0.005;                                  // 计算当前的位置在总长度上的比例[0,1]
if (currentValue >= 1) {currentValue = 0;
}measure.getPosTan(measure.getLength() * currentValue, pos, tan);        // 获取当前位置的坐标以及趋势mMatrix.reset();                                                        // 重置Matrix
float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI); // 计算图片旋转角度mMatrix.postRotate(degrees, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);   // 旋转图片
mMatrix.postTranslate(pos[0] - mBitmap.getWidth() / 2, pos[1] - mBitmap.getHeight() / 2);   // 将图片绘制中心调整到与当前点重合canvas.drawPath(path, mDeafultPaint);                                   // 绘制 Path
canvas.drawBitmap(mBitmap, mMatrix, mDeafultPaint);                     // 绘制箭头invalidate();                   
  1. 通过 tan 得值计算出图片旋转的角度,tan 是 tangent 的缩写,即中学中常见的正切, 其中tan[0]是邻边边长,tan[1]是对边边长,而Math中 atan2 方法是根据正切是数值计算出该角度的大小,得到的单位是弧度(取值范围是 -pi 到 pi),所以上面又将弧度转为了角度。
  2. 通过 Matrix 来设置图片对旋转角度和位移,这里使用的方法与前面讲解过对 canvas操作 有些类似,对于 Matrix 会在后面专一进行讲解,敬请期待。
  3. 页面刷新,页面刷新此处是在 onDraw 里面调用了 invalidate 方法来保持界面不断刷新,但并不提倡这么做,正确对做法应该是使用 线程 或者 ValueAnimator 来控制界面的刷新,关于控制页面刷新这一部分会在后续的 动画部分 详细讲解,同样敬请期待

3.6 getMatrix当前路径上某点的正切值矩阵

boolean getMatrix (float distance, Matrix matrix, int flags)
参数 作用 备注
返回值(boolean) 判断获取是否成功 true表示成功,数据会存入matrix中,false 失败,matrix内容不会改变
distance 距离 Path 起点的长度 取值范围: 0 <= distance <= getLength
matrix 根据 falgs 封装好的matrix 会根据 flags 的设置而存入不同的内容
flags 规定哪些内容会存入到matrix中 可选择:POSITION_MATRIX_FLAG(位置)ANGENT_MATRIX_FLAG(正切)

其实该方法就是把3.5中构建的切线矩阵,直接获取出来,我们看下对应的代码;

// 获取当前位置的坐标以及趋势的矩阵
measure.getMatrix(measure.getLength() * currentValue, mMatrix, PathMeasure.TANGENT_MATRIX_FLAG | PathMeasure.POSITION_MATRIX_FLAG);mMatrix.preTranslate(-mBitmap.getWidth() / 2, -mBitmap.getHeight() / 2);   // <-- 将图片绘制中心调整到与当前点重合(注意:此处是前乘pre)canvas.drawPath(path, mDeafultPaint);                                   // 绘制 Path
canvas.drawBitmap(mBitmap, mMatrix, mDeafultPaint);                     // 绘制箭头invalidate();      
  1. 对 matrix 的操作必须要在 getMatrix 之后进行,否则会被 getMatrix 重置而导致无效。
  2. 矩阵对旋转角度默认为图片的左上角,我们此处需要使用 preTranslate 调整为图片中心。
  3. pre(矩阵前乘) 与 post(矩阵后乘) 的区别,此处请等待后续的文章或者自行搜索

四、Path & SVG

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

你说什么是 SVG?

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

Path 和 SVG 结合通常能诞生出一些奇妙的东西,如下:

该图片来自这个开源库 ->PathView
SVG 转 Path 的解析可以用这个库 -> AndroidSVG

参考文献

  • 安卓自定义View进阶 - Path之基本操作
  • 安卓自定义View进阶 - Path之贝塞尔曲线
  • 安卓自定义View进阶 - Path完结篇
  • 安卓自定义View进阶 - PathMeasure

(4.1.36.1)Graphics图形学解析补充篇:路径Path相关推荐

  1. (4.1.36)android Graphics 图形学解析

    其实在写这篇文章的时候,我一直很犹豫是按照学习顺序进行布局,还是按照类型进行布局.最终我还是选择了按照类型进行布局,因此在顺序阅读上可能会存在一定的难度,如果实在觉得很难理解的部分,可以先自行跳过 本 ...

  2. mulesoft MCIA 易错题汇总解析(补充)

    mulesoft MCIA 易错题汇总解析(补充 1. What limits if a particular Anypoint Platform user can discover an asset ...

  3. C# 10 新特性 —— 补充篇

    C# 10 新特性 -- 补充篇 Intro 前面已经写了几篇文章介绍 C# 10 新特性的文章,还有一些小的更新 Constant interpolated strings 在之前的版本中,如果想要 ...

  4. 让你久等了《开源安全运维平台OSSIM疑难解析--入门篇》正式出版

    2019年暑期,众所期待的新书<开源安全运维平台OSSIM疑难解析--入门篇>由人民邮电出版社正式出版发行.此书从立意到付梓,历时超过两年,经过数十次大修,历经曲折与艰辛,希望为大家代奉献 ...

  5. 让你久等了!《开源安全运维平台OSSIM疑难解析--入门篇》9月上市

    2019年暑期,众所期待的新书<开源安全运维平台OSSIM疑难解析--入门篇>开始印刷,9月份即可预售.此书从立意到付梓,历时超过两年,经过数十次大修,历经曲折与艰辛,希望为大家代奉献一本 ...

  6. ROS学习笔记(一)补充篇 参考创客制造

    我将ROS的CPP部分分成7个部分: 1.基础的node param 2.动态调节参数 3.关于TF变换 4.actionlib 5.插件技术 6.movebase 7.nodelet技术 前言 相比 ...

  7. 浅谈动感歌词-歌词补充篇

    1引言 之前写了几篇关于动感歌词的简单介绍,相信大家还有印象,这里就不多说了,这篇要说的是,关于翻译歌词和音译歌词,以及我在解析和显示这两种歌词的时候,遇到的一些难题.技术和心得. 2动感歌词格式 下 ...

  8. python爬虫用途-Python爬虫入门知识:解析数据篇

    首先,让我们回顾一下入门Python爬虫的四个步骤吧: 而解析数据,其用途就是在爬虫过程中将服务器返回的HTML源代码转换为我们能读懂的格式.那么,接下来就正式进入到解析数据篇的内容啦. Part 1 ...

  9. go 发送http请求; Golang 解析JSON 篇

    https://www.runoob.com/go/go-fmt-sprintf.html go 发送http请求: package mainimport ("io/ioutil" ...

最新文章

  1. MEMS传感器科普文
  2. python语音播报-用Python写一个语音播放软件
  3. 把企业分“三只鸟”的发展好比“三个策略”
  4. 基于TextRank的关键词提取算法
  5. gtx780有html接口吗,笔记本玩转游戏大作 达人外接GTX780Ti
  6. mp4 视频在网页上播放不了
  7. 机器学习的训练数据(Training Dataset)、测试数据(Testing Dataset)和验证数据(Validation Dataset)
  8. Easy machine learning pipelines with pipelearner: intro and call for contributors
  9. linux 笔记本 显卡驱动下载地址,AMD Radeon HD系列Linux显卡驱动13.9版下载
  10. mysql表设计ppt_PPT表格太丑?这3个设计细节,你一定要收藏!
  11. 【Derivation】 条件数学期望公式泊松分布推导(Poisson distribution)
  12. 企业如何选择?网站建设中常见的几种类型
  13. 转载:50有用的JavaScript和jQuery技术和插件
  14. Linux下的motion detection(最简单的办公室监控系统) 邮件自动发送
  15. AS中的typo作用
  16. Hostapd.conf详细释义
  17. 服务器怎么安装debian系统,图解Debian10Linux系统的安装步骤
  18. Mac U盘安装High Sierra
  19. 上海都有哪些牛逼的互联网公司?
  20. uni-app获取元素高度

热门文章

  1. Simpsons’ Hidden Talents——kmp入门
  2. 2013华为工作之研究所行
  3. 腾讯后台面经大全(整合版)
  4. 网络基础知识学习笔记
  5. PDK工艺库安装-CSMC
  6. java答辩记录问题,S2_javaWeb答辩问题集 qlzx
  7. 国家列表 Country Code List
  8. OpenCV开发笔记(三十一):红胖子8分钟带你深入了解双阈值化(图文并貌+浅显易懂+程序源码)
  9. Infiniband网络测速
  10. 【评测】细胞STR鉴定服务商服务内容解析