android Path 和 PathMeasure 进阶

Posted 麻花儿wt

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android Path 和 PathMeasure 进阶相关的知识,希望对你有一定的参考价值。

1 概述

在前面的路径和文字中,讲解了path的基本用法,这里讲解一些上篇没有讲到的东西。

2 Path

这里讲解path相关的方法,后面继续讲解PathMeasure,以及实例

(1) offset

public void offset(float dx, float dy)
public void offset(float dx, float dy, Path dst)

这里两个方法都是指定offset,使得path偏移。其中第二个方法中的dst代表了移动后的path写入的目标。如果为null,则会写入调用该方法的path中。

这里写图片描述

这里使用了第一个方法,代码如下

Path path = new Path();
path.moveTo(0, 0);
path.lineTo(200, 200);
canvas.drawPath(path, paint);

path.offset(500, 0);
canvas.drawPath(path, paint);

再看看第二个方法的用法:

这里写图片描述

代码如下

Path path = new Path();
path.moveTo(0, 0);
path.lineTo(200, 200);
canvas.drawPath(path, paint);

Path path1 = new Path();
path1.moveTo(0, 0);
path1.lineTo(100, 100);
path1.offset(500, 0, path);
canvas.drawPath(path, paint);

看这两句代码

path1.offset(500, 0, path);
canvas.drawPath(path, paint);

可以看出,偏移后的路径被写入了path中。

(2) FillType

FillType,有点类似前面讲解的xfermode,不过这里比较简单,只有有四个值,如下:

WINDING//默认值,取两个图形相交
EVEN_ODD//取不相交的部分
INVERSE_WINDING//反转相交
INVERSE_EVEN_ODD//反转不相交部分

来看看实验的代码:

Path path = new Path();
path.addRect(100, 100, 500, 500, Path.Direction.CW);
path.addCircle(500, 500, 300, Path.Direction.CW);
path.setFillType(Path.FillType.WINDING);
canvas.drawPath(path, paint);

这里使用的默认的WINDING,来看看各种FillType对应的图形:

WINDING

这里写图片描述

EVEN_ODD

这里写图片描述

INVERSE_WINDING

这里写图片描述

INVERSE_EVEN_ODD

这里写图片描述

看了上面的图片,四种模式基本就清晰了。

(3) reset

reset放在FillType后面讲解,就是因为它和reset有关系,reset会清空path的所有数据,但是不会清空FillType。我们用一段代码来证实:

paint.setColor(Color.BLUE);
Path path = new Path();
path.addRect(100, 100, 500, 500, Path.Direction.CW);
path.addCircle(500, 500, 300, Path.Direction.CW);
path.setFillType(Path.FillType.INVERSE_WINDING);
path.reset();
canvas.drawPath(path, paint);

看上面的代码,如果FillType被清除,那么这里绘制的将是一个空的内容,整个界面将是白色的,然而图片如下:

这里写图片描述

这里可以看到,并不是白色,而是蓝色,因为这里的FillType设置的是INVERSE_WINDING,也就是反转,由于它没有被清除掉,那么空路径的补集就是全集,所以整个屏幕被绘制为蓝色。

(4) rewind

rewind和reset类似,但是rewind会清除掉FillType以及所有的直线,曲线,点的数据等,但是他会保留数据结构,这样可以快速重用,提高一定的性能,例如说,重复绘制一类线段,他们的点的数量都相等,那么使用rewind可以保留装载点数据的数据结构,效率会更高。

我们来验证它是否会清除FillType

paint.setColor(Color.BLUE);
Path path = new Path();
path.addRect(100, 100, 500, 500, Path.Direction.CW);
path.addCircle(500, 500, 300, Path.Direction.CW);
path.setFillType(Path.FillType.INVERSE_WINDING);
path.rewind();
canvas.drawPath(path, paint);

效果如下

这里写图片描述

可以看出,所有数据包括FillType都被清除了。

3 PathMeasure

PathMeasure主要用来测量path,通过它,我们可以得到路径上特定的点的坐标等等。先看看他的基本方法。

(1) 构造方法

public PathMeasure()
public PathMeasure(Path path, boolean forceClosed)

如上,有两个方法,第一个就不讲解了,第二个方法中有两个参数;

path:需要测量的path
forceClosed:是否关闭path

(2) setPath

public void setPath(Path path, boolean forceClosed)

这里就是指定需要测量的path,基本和上面的第二个构造函数类似。

(3) getLength

返回当前path的总长度。

这里写图片描述

代码如下:

path.addRect(100, 100, 500, 500, Path.Direction.CW);
canvas.drawPath(path, paint);
paint.setStyle(Paint.Style.FILL);
PathMeasure pathMeasure = new PathMeasure(path, false);
float length = pathMeasure.getLength();
canvas.drawText(String.valueOf(length), 500, 500, paint);

可以看到,这里绘制的1600就是这个path轮廓的长度。

(4) getPosTan

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

返回值是boolean,如过path为空,则返回false
传入参数有三个:
distance:传入距离起点的距离。
pos[]:意思是position,分别对应点的x,y坐标
tan[]:这个值比较难以理解。我们下面讲解下这个值的意义。

先来看一个动图:

这里写图片描述

代码如下:

private float[] pos;
private float[] tan;
//绘制动画路径
canvas.drawPath(animPath, paint);

if(distance < pathLength){
//获取位置和
pathMeasure.getPosTan(distance, pos, tan);

matrix.reset();
//计算方位角
float degrees = (float)(Math.atan2(tan[1], tan[0])*180.0/Math.PI);
//计算变换矩阵,用于按照方位角旋转移动的那个图像(矩阵后面有篇幅讲解,这里先不管)
matrix.postRotate(degrees, bm_offsetX, bm_offsetY);
matrix.postTranslate(pos[0]-bm_offsetX, pos[1]-bm_offsetY);

//按照矩阵(包括了旋转和位移)绘制图像
canvas.drawBitmap(bm, matrix, null);

distance += step;
}else{
distance = 0;
}

invalidate();

从上面的图中可以看到,移动的图形,不仅仅只是移动,还进行了旋转,那么这个旋转的角度如何得来,这里就靠这句代码

pathMeasure.getPosTan(distance, pos, tan);

里面的tan就是计算旋转方位角的关键。那么这个tan代表的意义何在呢,我们知道,移动图形最开始的时候的x坐标系是和cavans的平行的,在后面的移动中,这个黑色的火焰进入了一个斜线,然后他发生的旋转,此时,他的x坐标系不再平行于canvas坐标系。这里看一张图:

这里写图片描述

图中红色的线代表了黑色火焰的x轴,可以看到,他原本是平行于canvas的x轴的,后来在进入第二个斜边的时候,他沿着蓝色箭头旋转了,此时红线不再平行于canvas的x轴,红线和单位圆的交点的x,y坐标就是这里返回的tan值。通过这张图片这里的意义已经非常清晰了。

(5) getMatrix

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

这个方法和上面的其实类似,只是他返回的是一个处理好的matrix,但是这个matrix是以左上角作为旋转点,所以需要将这个点移动到中心点。
其中还多了一个参数flags,指的是这个martrix需要什么信息。flags的值有如下两个
PathMeasure.POSITION_MATRIX_FLAG:位置信息
pathMeasure.TANGENT_MATRIX_FLAG:切边信息,方位角信息,使得图片按path旋转。

代码如下:

matrix.reset();
pathMeasure.getMatrix(distance, matrix, PathMeasure.POSITION_MATRIX_FLAG | pathMeasure.TANGENT_MATRIX_FLAG);
matrix.preTranslate(-bm_offsetX, -bm_offsetY);
canvas.drawBitmap(bm, matrix, null);

其他的相同就不贴了,这里主要是需要做一个旋转点的变换:

matrix.preTranslate(-bm_offsetX, -bm_offsetY);

(6) getSegment

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

这个方法返回boolean,如果截取的长度为0则返回false,否则为true。参数意义如下

startD:起始距离
stopD:终点距离
dst:接收截取的path
startWithMoveTo:是否把截取的path,moveto到起始点。

来看一个例子:

这里写图片描述

代码如下

path.addRect(300, 300, 700, 700, Path.Direction.CW);
canvas.drawPath(path, paint);
PathMeasure pathMeasure = new PathMeasure(path, false);

Path dstPath = new Path();
pathMeasure.getSegment(0, 800, dstPath, false);
paint.setColor(Color.RED);
canvas.drawPath(dstPath, paint);

可以看到,由于这里的startWithMoveTo参数设置的是false,那么截取的path就没有moveTo到起始位置,则默认moveTo到(0,0)所以导致了绘制的红线从原点开始。设置了ture之后,图片如下:

这里写图片描述

可以看到重合了。

好了,Path和PathMeasure就到这里,上面的例子大家可以多多参考。

以上是关于android Path 和 PathMeasure 进阶的主要内容,如果未能解决你的问题,请参考以下文章

Android之Glide获取图片Path和Glide获取图片Bitmap

Android中Path类的lineTo方法和quadTo方法画线的区别

Android Canvas Path 和 Paint 实例 杏彩出租游戏开发必备

如何序列化 android.graphics.Path 的对象

Android自定义控件-Path之贝赛尔曲线和手势轨迹水波纹效果

Android 通过uri获取文件路径path