摘自:https://github.com/GcsSloop/AndroidNote
一、Path的基本操作
作用:绘制复杂的图形,绘制复杂的文本和图形剪切
简介:Path是封装了由直线和曲线(二次,三次贝塞尔曲线)构成的几何路径。所以说需要明白直线和贝塞尔曲线的绘制。
第1组: moveTo、 setLastPoint、 lineTo 和 close(直线的绘制)
(1)moveTo()
方法:publicvoid moveTo (float x, float y)
作用:我们知道两点确定一条直线,moveTo()确定直线的起点在哪里。,需要和lineTo()一起使用。等会跟lineTo()一起演示
(2)lineTo()
方法:public void lineTo (float x, float y)
作用:确定直线的终点。
那么lineTo()的起点是怎么确定的呢:
①、首先如果没有使用moveTo()的情况下,则为cavnas画布的原点位置。
②、如果使用moveTo()的情况下,则为moveTo()指定的位置。
③、第二次lineTo()的起点,为第一次lineTo()的终点。
测试代码:
①、第一步:创建公用的画笔
<span style="font-size:18px;">public PathView(Context context) {
super(context);
init();
}
public PathView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
//第一步:自定义画笔
mPaintPath = new Paint();
mPaintPath.setColor(Color.BLACK);
mPaintPath.setStyle(Paint.Style.STROKE);
mPaintPath.setStrokeWidth(5);
}</span>
<span style="font-size:18px;"> //图一:直接使用lineTo()
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Path path = new Path();
path.lineTo(200,200);
//绘制路径
canvas.drawPath(path,mPaintPath);
}
//图二:调用moveTo()再使用lineTo()
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Path path = new Path();
path.moveTo(200,200);
path.lineTo(200,400);
canvas.drawPath(path,mPaintPath);
}
</span>
记得修改activity_main.xml
③、setLastPoint
方法:public void setLastPoint(float x,float y)
作用:重置之前操作的最后一个点位置。也就是说之前lineTo()设置点终点被修改
测试:(我们在图三的代码上,加了setLastPoint())
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Path path = new Path();
path.moveTo(200,200);
path.lineTo(200,400);
//第二条线
path.lineTo(400,200);
//修改第二条线的终点
path.setLastPoint(400,400);
canvas.drawPath(path,mPaintPath);
}
结果:
④、close()
作用:故名思意是用来闭合路径用的
测试:(我们也在图三上加上close()吧)
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Path path = new Path();
path.moveTo(200,200);
path.lineTo(200,400);
//第二条线
path.lineTo(400,200);
//闭合路径
path.close();
canvas.drawPath(path,mPaintPath);
}
结果:
发现:连接终点与起点的直线。
注意:如果连接了终点和起点还是无法形成封闭图形的话,则close()什么也不做。
第二组:
addXxx与arcTo
①、addXxx()
作用:同cavnas类中绘制各种各样的图形
方法预览:
// 第一类(基本形状)
// 圆形
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)
Path.Direction 是枚举类型:表示该图像是顺时针绘制还是逆时针绘制。
顺时针:CW 逆时针是:CCW
关于顺时针逆时针的作用需要之后才能解释。
②、了解Path.addPath()
作用:将两个路径合并。
方法:
// 第二类(Path) // path public void addPath (Path src) public void addPath (Path src, float dx, float dy) public void addPath (Path src, Matrix matrix)测试:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//将画布移到中心位置
canvas.translate(mViewWidth/2,mViewHeight/2);
//创建一个路径绘制正方形
Path rectPath = new Path();
rectPath.addRect(-200,-200,200,200,Path.Direction.CW);
//创建一个路径绘制圆形
Path circlePath = new Path();
circlePath.addCircle(0,0,200, Path.Direction.CW);
//合并路径,设置circlePath左上角的偏移量为0
rectPath.addPath(circlePath,0,0);
//绘制路径
canvas.drawPath(rectPath,mPaintPath);
}
结果:
③、了解:addArc()与arcTo()
作用:都是用来绘制圆弧的
// 第三类(addArc与arcTo) // addArc public void addArc (RectF oval, float startAngle, float sweepAngle) // arcTo public void arcTo (RectF oval, float startAngle, float sweepAngle) public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
forceMoveTo是什么作用呢?
这个变量意思为“是否强制使用moveTo”,也就是说,是否使用moveTo将变量移动到圆弧的起点位移,也就意味着:
forceMoveTo | 含义 | 等价方法 |
---|---|---|
true | 不连接最后一个点与弧的起点,同addArc()方法 | public void addArc (RectF oval, float startAngle, float sweepAngle) |
false | 而是连接最后一个点与圆弧起点,arcTo默认为false | public void arcTo (RectF oval, float startAngle, float sweepAngle) |
//图一:使用arcTo为true的情况
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//将画布移到中心位置
canvas.translate(mViewWidth/2,mViewHeight/2);
//创建一条直线
Path path = new Path();
path.moveTo(-200,0);
path.lineTo(0,0);
//创建一条圆弧
RectF rect = new RectF(0,-200,200,0);
path.arcTo(rect,270,180,true);
//创建一条直线
path.lineTo(100,-200);
canvas.drawPath(path,mPaintPath);
}
//图二:使用addArc()
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//将画布移到中心位置
canvas.translate(mViewWidth/2,mViewHeight/2);
//创建一条直线
Path path = new Path();
path.moveTo(-200,0);
path.lineTo(0,0);
//创建一条圆弧
RectF rect = new RectF(0,-200,200,0);
path.addArc(rect,270,180);
//创建一条直线
path.lineTo(100,-200);
canvas.drawPath(path,mPaintPath);
}
//图三:使用arcTo()设置foreMoveTo为false的情况
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//将画布移到中心位置
canvas.translate(mViewWidth/2,mViewHeight/2);
//创建一条直线
Path path = new Path();
path.moveTo(-200,0);
path.lineTo(0,0);
//创建一条圆弧
RectF rect = new RectF(0,-200,200,0);
path.arcTo(rect,270,180,false);
//创建一条直线
path.lineTo(100,-200);
canvas.drawPath(path,mPaintPath);
}
结果:
图一 图二 图三
第3组:isEmpty、 isRect、 set 和 offset
①、isEmpty
作用:判断当前Path内容是否为空
②、isRect
作用:判断当前Path是否为一个矩形
③、set
作用:利用另一个Path重置当前Path
④、offset
作用:偏移当前Pah 的位置
方法预览:
public void offset (float dx, float dy)
public void offset (float dx, float dy, Path dst)
但是第二个方法最后怎么会有一个path作为参数?
其实第二个方法中最后的参数das是存储平移后的path的。
dst状态 | 效果 |
---|---|
dst不为空 | 将当前path平移后的状态存入dst中,不会影响当前path |
dat为空(null) | 平移将作用于当前path,相当于第一种方法 |
二、贝塞尔曲线原理
详情参照:贝塞尔曲线的原理
①、贝塞尔二阶曲线的使用
方法:public void qualTo(float controlX,float controlY,float endX,float endY);
使用:
Path path = new Path();
//曲线的起始点
path.moveTo(mStartPoint.x,mStartPoint.y);
//曲线的控制点和终止点
path.quadTo(mControlPoint.x,mControlPoint.y,mEndPoint.x,mEndPoint.y);
canvas.drawPath(path,mPaint);
②、贝塞尔三阶曲线的使用
方法:public void cubicTo(float x1,float y1,float x2,float y2,float x3,float y3);
使用:原理同上。
三、Path的进阶
①、rXxx()方法(rMoveTo, rLineTo, rQuadTo, rCubicTo)
作用:相对于上一点的坐标,进行偏移。
lineTo(float x, float y) 的终点是相对于Cavnas的原点作为坐标点,进行偏移的。
而rLineTo(float x,float y)的终点是相对于上一个点作为坐标点,进行平移的。
也就是说上一个点是(200,200) 然后调用rLineTo(200,200),则rLineTo()的实际值是(200+200,200+200)
②、图形的填充模式
1、电脑如何判断图形的内外
奇偶规则、非零环绕数规则:两种规则的原理
Android中的填充模式
Android中的填充模式有四种,是封装在Path中的一个枚举。
模式 | 简介 |
---|---|
EVEN_ODD | 奇偶规则 |
INVERSE_EVEN_ODD | 反奇偶规则 |
WINDING | 非零环绕数规则 |
INVERSE_WINDING | 反非零环绕数规则 |
我们可以看到上面有四种模式,分成两对,例如 "奇偶规则" 与 "反奇偶规则" 是一对,它们之间有什么关系呢?
Inverse 和含义是“相反,对立”,说明反奇偶规则刚好与奇偶规则相反,例如对于一个矩形而言,使用奇偶规则会填充矩形内部,而使用反奇偶规则会填充矩形外部,这个会在后面示例中代码展示两者对区别。
Android与填充模式相关的方法
这些都是Path中的方法。
方法 | 作用 |
---|---|
setFillType | 设置填充规则 |
getFillType | 获取当前填充规则 |
isInverseFillType | 判断是否是反向(INVERSE)规则 |
toggleInverseFillType | 切换填充规则(即原有规则与反向规则之间相互切换) |
三、PathMeasure的使用
作用:用来测量Path的。
构造方法:
PathMeasure() | 创建一个空的PathMeasure |
PathMeasure(Path path, boolean forceClosed) | 创建 PathMeasure 并关联一个指定的Path(Path需要已经创建完成)。 |
boolean foreClosed:表示测量是否是否使Path闭合(如果能够闭合的上的话)。
容易误解的地方:①、仅仅表示测量的时候闭合,之后进行一系列的测量计算。而不是将Path这个对象的图像闭合。
或者说PathMeasure假想path是闭合的图像,然后进行测量。而path本身是没有闭合的。
常用方法:
void | setPath(Path path, boolean forceClosed) | 关联一个Path,作用跟构造方法2是相同的 |
boolean | isClosed() | 检查path,是否闭合 |
float | getLength() | 获取Path的长度,长度指的是周长。 |
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 | 起始点是否使用 moveTo | 用于保证截取的 Path 起始点为原先位置的起始点。 |
//图一
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//将画布放到中间来
canvas.translate(mViewWidth/2,mViewHeight/2);
//创建一个圆形
Path path1 = new Path();
path1.addCircle(0,0,200, Path.Direction.CW);
//创建一条直线
Path path2 = new Path();
path2.moveTo(-200,0);
path2.lineTo(200,0);
//绘制图
canvas.drawPath(path1,mPaint);
canvas.drawPath(path2,mPaint);
}
//图二
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//将画布放到中间来
canvas.translate(mViewWidth/2,mViewHeight/2);
//创建一个圆形
Path path1 = new Path();
path1.addCircle(0,0,200, Path.Direction.CW);
//创建一条直线
Path path2 = new Path();
path2.moveTo(-200,0);
path2.lineTo(200,0);
//获取一半的圆,加入到直线中。注意时针顺序问题
PathMeasure measure = new PathMeasure(path1,false);
measure.getSegment((float) Math.PI*200,(float) Math.PI*200*2,path2,true);
canvas.drawPath(path2,mPaint);
}
startWithMoveTo 为 false,则会将截取出来的 Path 片段的起始点移动到 dst 的最后一个点,以保证 dst 的连续性。这个例子看不出来。
boolean | nextContour() | 跳转到下一个轮廓 |
有这么一张图,如果使用PathMeasure测量Path,得到的是最外部的Path的周长。那么怎么获取内部Path的周长呢。
就需要用到nextContour()方法,调到内部去。
boolean | getPosTan(float distance, float[] pos, float[] tan) | 获取指定长度的位置坐标及该点切线值 |
boolean | getMatrix(float distance, Matrix matrix, int flags) | 获取指定长度的位置坐标及该点Matrix。可以说将方法一的数据放到了Matrix中 |
我现在有一个圆
我想获取红色点x,y的坐标点,和该位置想x,y的正切值(圆的趋势)。
我们就可以先获取该点在圆的周长上的位置,调用该方法放入数组中。
实例:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//将画布放到中间来
canvas.translate(mViewWidth/2,mViewHeight/2);
//创建一个圆形
Path path1 = new Path();
path1.addCircle(0,0,200, Path.Direction.CW);
//获取圆1/2周长位置的值
PathMeasure pathMeasure = new PathMeasure(path1,false);
//创建获取数据的容器
//pos 代表 x,y坐标
float [] pos = new float[2];
//tan 代表 当前
float [] tan = new float[2];
//调用方法获取数据
pathMeasure.getPosTan((float)Math.PI*200,pos,tan);
Log.d("PathMeasure","pos"+pos[0]+" "+pos[1]+" tan"+tan[0]+" "+tan[1]);
}