Android中Path的简单使用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MonaLisaTearr/article/details/79571454

        由于canvas自带的drawXxx()方法能绘制的都是一些相对简单和比较规则的图形,所以当需要绘制一些比较复杂的2D图形的时候后canvas就显得力不从心了,所以就有了Path了,其主要用于绘制一些比较复杂的图形轮廓的,比如所多边形之类的,只要你计算好坐标,理论上任何2D图形都可以绘制得出来,下面大概的看下方法列表吧。 

一、Path常用方法以及 

方法名  描述 
Path()  构造方法,当然还有其他的 
reset()  重置path至初始化时状态,不保留内部数据结构(保留Filltype) 
rewind()  重置path至初始化时状态 ,但是会保留内部数据结构以便重用 
set(Path)  将一个path赋值给当前的path,当前path的设置会被覆盖掉 
setFillType(FillType)  填充模式设置 
isInverseFillType()  填充模式判断 
toggleInverseFillType()  填充模式切换 
isEmpty()  判断Path是否为空,这里的空不是指null,而是只path对象是否有设置过 
isRect(RectF)  判断传入RectF对象是否为矩形 
computeBounds(RectF,boolean)  计算path的边界 
incReserve(int)  提醒系统,还有多少点要加入(个人并不知道有什么卵用,可能跟性能有关吧) 
moveTo(float,float)  移动到某个点,下次操作时从该点开始(基于原点坐标系的偏移量) 
rMoveTo(float,float)  移动到某个点,下次操作时从该点开始(基于当前点坐标系的偏移量) 
lineTo(float,float)  绘制上一个点到参数中传入点的path(基于原点坐标系的偏移量) 
rLineTo(float,float)  绘制上一个点到参数中传入点的path(基于当前点坐标系的偏移量) 
quadTo(float,float,float,float)  绘制二次的贝塞尔曲线 (基于原点坐标系的偏移量)
rQuadTo(float,float,float,float)  绘制二次的贝塞尔曲线 (基于当前点坐标系的偏移量)
cubicTo(float,float,float,float,float,float)  绘制三次的贝塞尔曲线 (基于原点坐标系的偏移量)
rCubicTo(float,float,float,float,float,float)  绘制三次的贝塞尔曲线 (基于当前点坐标系的偏移量)
arcTo(RectF,float,float,boolean)   
close()  封闭,通俗的说就是连接起点跟终点 
addRect(RectF,Direction)  添加矩形到当前path 
addOval(RectF,Direction)  添加椭圆到当前path 
addCircle(float,float,float,Direction)  添加圆到当前path 
addArc(RectF,float,float)  添加圆弧到当前path 
addRoundRect(RectF,float,float,Direction)  添加圆角矩形到当前path 
addPath(Path,float,float)  添加path到当前path 
op(Path,Path,Op)  对两个Path进行布尔值运行,也就是谁遮挡谁,或者取交集,取并集等等, 
setLastPoint(float,float)  设置最后一个点的位置 
transform(Matrix,Path)  矩阵变化(这个好难,暂时不太懂) 

大概明白意思的方法就那么多了,当然还有些重载的方法,那些就不说了,自行查阅文档吧!接下来对这些方法做做试验,毕竟不亲自试过很难放心!

二、Path各个方法、方法参数基本演示

        开始之前我们首先创建Paint跟Path对象,虽然现在只不过是测试,但是还是严谨点的好,养成不要在onDraw()方法中创建对象的习惯(有些为了演示方便,直接在onDraw()中创建对象了,罪过。。。)
    //创建画笔
    paint = new Paint();
    paint.setColor(Color.RED);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(10.0f);
    paint.setAntiAlias(true);

    //创建Path对象
    path = new Path();
1、moveTo(float,float),lineTo(float,float),先从这个两方法入手,往往moveTo 这个方法都会在开始时候调用(当然中途也可以),以达到调整下一次操作的起始位置,下面看看:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    //设置画布背景颜色
    canvas.drawColor(Color.YELLOW);

    /**
    * 没有调用moveTo方法,所以默认从画布坐标(0,0)开始
    * 参数1:x坐标偏移量
    * 参数2:y坐标偏移量
    */
    path.lineTo(200,200);

    /**
    * 调整下一次操作的起点位置为(200,0)
    */
    path.moveTo(200,0);

    /**
    * 从(200,0)开始画一条道(200,400)的仙
    */
    path.lineTo(200,400);
    canvas.drawPath(path,paint);
}

运行结果:


从上图可以看出,(200,400)这个点参考的坐标系还是(0,0)那个点,也就是说,不管我们后面调用多少次lineTo方法,都是以(0,0)为参考点的(没有移动过canvas的情况下),这点很重要,因为后面会介绍rlineTo方法,这个方法就不一样了。 

2、为了更直观的区分带r开头的跟不带r开头的方法,我们接下来把1中的方法改为调用rLineTo(float,float)跟rMoveTo(float,float),看看结果会怎么样 

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    //设置画布背景颜色
    canvas.drawColor(Color.YELLOW);
    /**
    * 没有调用moveTo方法,所以默认从画布坐标(0,0)开始
    * 参数1:x坐标偏移量
    * 参数2:y坐标偏移量
    */
    path.rLineTo(200,200);

    /**
    * 调整下一次操作的起点位置为(200,0)
    * 注意坐标系的变化
    */
    path.rMoveTo(200,0);

    /**
    * 从(200,0)开始画一条道(200,400)的线
    */
    path.lineTo(200,400);
    canvas.drawPath(path,paint);
}

运行结果如下:


同样的参数,相差太大了,为了方便的理解,加了黑色跟绿色线条的两个坐标系,当第一次调用rLineTo的时候,是以A点为原点的坐标系为参考的,当调用rMoveTo的时候,这就跟之前的不一样了,坐标系不再是A,而是变成了A坐标系中的(200,200)也就是B,所以C点的偏移量就是以B点为原点的坐标系的偏移量,同理,当再次调用rLineTo的偏移量就变成了以C点为原点的坐标系的偏移量了! 

扫描二维码关注公众号,回复: 6231993 查看本文章

3、接下来看看close()方法,看字面意思就知道,关闭嘛,其实就是闭合起点跟终点: 

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    //设置画布背景颜色
    canvas.drawColor(Color.YELLOW);

    /**
    * 移动起点到水平居中位置
    */
    path.moveTo(getMeasuredWidth()/2,10);

    /**
    * 画三角形的其中两条边
    */
    path.lineTo(10,getMeasuredHeight()-10);
    path.rLineTo(getMeasuredWidth()-20,-10);
    path.close();
    canvas.drawPath(path,paint);
}

运行结果如下:


可以看到,在代码中,我们只是调用了一次lineTo跟一次rLineTo方法,也就是说只绘制了两条线,但是却得了一个三角形出来,这就时候因为调用了close方法的缘故,将启动跟终点相连了。 

4、接下来演示先addRect()、addCircle()、addArc()、addOval()这几个方法,代码如下: 

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    //设置画布背景颜色
    canvas.drawColor(Color.YELLOW);

  /**
    * 将坐标系的原点移动到控件的中心(这个是之前整理canvas部分笔记的时候漏掉了,包括canvas的旋转)
    * 这些操作能够让很多绘制省去很多繁琐的计算,后面再单独进行整理
    */
    canvas.translate(getMeasuredWidth()/2,getMeasuredHeight()/2);

    /**
    * 下面是创建圆、矩形、圆弧、椭圆的轨迹的代码,并添加到path中,这里就不赘述了,canvas那个笔记里面已有详细说明
    * 而且这里要强调的是,这里为了演示方便,所以直接在onDraw()中创建Path对象,在实际开发中请避免
    */
    Path circle = new Path();
    circle.addCircle(-getMeasuredHeight()/4,-getMeasuredWidth()/4,getMeasuredHeight()/4-30, Path.Direction.CW);

    Path rect = new Path();
    rect.addRect(30,-getMeasuredHeight()/2+30,getMeasuredWidth()/2-30,-30,Path.Direction.CW);

    Path arc = new Path();
    RectF rectF = new RectF(-getMeasuredWidth()/2+30,30,-30,getMeasuredHeight()/2-30);
    arc.addArc(rectF,0,180);

    Path oval = new Path();
    RectF ovalF = new RectF(30, 100, getMeasuredWidth() / 2 - 30, getMeasuredHeight() / 2 - 100);
    oval.addOval(ovalF, Path.Direction.CW);

    /**
    * 合并所有的path,方便一次性绘制出来
    */
    circle.addPath(rect);
    circle.addPath(arc);
    circle.addPath(oval);

    canvas.drawPath(circle,paint);
}

运行结果如下:


上面还用到了addPath()方法,这个方法其实就是合并path用的,这样就方便一起绘制了,他还有几个重载方法,可以自行查阅文档了解!

5、setLastPoint(float,float),这个方法是重置最后一个点的位置,也就是说,不管你前面的代码定义的最后一个点是在什么地方,如果最后有调用这个方法的话,最后一个点的位置是以setLastPoint(float,float)所定义的位置是一致的:
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    //设置画布背景颜色
    canvas.drawColor(Color.YELLOW);

    //将坐标系的原点移动到控件的中心
    canvas.translate(getMeasuredWidth()/2,getMeasuredHeight()/2);

    /**
    * 创建矩形
  * 而且这里要强调的是,这里为了演示方便,所以直接在onDraw()中创建Path对象,在实际开发中请避免
  */
Path path = new Path(); RectF rectF = new RectF(-200, -200, 200, 200); path.addRect(rectF, Path.Direction.CW); /** * 重置最后一个点的位置 */ path.setLastPoint(-50,50); canvas.drawPath(path,paint);}

运行结果如下:


运行的时候看到的确实这个效果,惊喜吧,意外吧!其实是因为D这个点被重置了,变成了(-50,50),所以图形就变成这个鬼样子了!

6、看了例5你会不会有个疑问,为什么最后一个点是D呢?为什么不是A?为什么不是C?首先,绘制矩形时第一个参数是left,那么从A点开始是可以理解的,并且,画矩形的方法内部实际上是用lineTo()方法不断的绘制边,那么从A这个点开始绘制的时候是怎么让程序知道是往B->C->D还是往D->C-B这个方向走呢?这可是直接就决定了最后一个点是D还是B,其实将图像轨迹添加到path的方法path.addRect(rectF, Path.Direction.CW); 中还有一个参数,就是最后一个,就决定了绘制的时候是顺时针还是逆时针绘制。

Path.Direction.CW  顺时针 
Path.Direction.CCW  逆时针 
那我们试着改变例子5中的这个参数看看是不是跟我们预期的一样,B点会跑到(-50,50)这个坐标: 
/**
* 创建矩形
*/
Path path = new Path();
RectF rectF = new RectF(-200, -200, 200, 200);
path.addRect(rectF, Path.Direction.CCW);


很明显,B点确实被重置了!!!

7、接下来介绍op(Path,Op),这个方法只,第一个参数是Path,那应该是两个path之间的事情了,那他们之间要以什么方式,发生什么事,怎么发生,那估计就跟第二个参数又关了,废话少说,先看代码:

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    //设置画布背景颜色
    canvas.drawColor(Color.YELLOW);

    //将坐标系的原点移动到控件的中心
    canvas.translate(getMeasuredWidth()/2,getMeasuredHeight()/2);

    //设置画笔
    paint.setColor(Color.RED);
    paint.setStyle(Paint.Style.FILL);
    paint.setStrokeWidth(10.0f);
    paint.setAntiAlias(true);

    Path circle1 = new Path();
    circle1.addCircle(-100,0,150, Path.Direction.CW);

    Path circle2 = new Path();   
    //确定circle2的位置
    circle2.addCircle(100,0,150, Path.Direction.CW);
    circle1.op(circle2, Path.Op.DIFFERENCE);

    canvas.drawPath(circle1,paint);
}

运行结果:

在上面代码中,我们画了两个实心圆的,由于我们使用了Path.Op.DIFFERENCE对两个圆进行了差异运算,所以circle2其实是已经看不到的了,上图中的空心圆只是我手动加上去方便理解而已! 

下面罗列下不同模式下不同的显示(注意,实心的色块才是真正的显示,空心的是被op方法干掉的,只是为了方便观察,p图的时候加上去的!!) 


ok,关于op方法就先说到这了!

8、最后来说说setFillType(FillType),这个叫填充模式,有些专业的叫法叫做什么奇偶环绕、非0奇偶环绕什么的,反正我是看得不太懂,只能自己写些代码来验证,下面看看效果怎么样:

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    //设置画布背景颜色
    canvas.drawColor(Color.YELLOW);

    //将坐标系的原点移动到控件的中心
    canvas.translate(getMeasuredWidth()/2,getMeasuredHeight()/2);

    //设置创建画笔
    paint.setColor(Color.RED);
    paint.setStyle(Paint.Style.FILL);
    paint.setStrokeWidth(10.0f);
    paint.setAntiAlias(true);

    //创建两个圆的path
    Path path = new Path();
    path.addCircle(-100, 0, 150, Path.Direction.CW);
    path.addCircle(100, 0, 150, Path.Direction.CW);

    //一、这是默认模式,取path所有占用的区域
    //path.setFillType(Path.FillType.WINDING);

    //二、取path未占用区域
    //path.setFillType(Path.FillType.INVERSE_WINDING);

    //三、取path所占用但不相交的区域
    //path.setFillType(Path.FillType.EVEN_ODD);

    //四、取path未占用区域或者已相交区域
    path.setFillType(Path.FillType.INVERSE_EVEN_ODD);

    canvas.drawPath(path,paint);
}

运行结果:


效果就是这样的了,至于比较专业的叫法什么的,自行研究吧,还有就是当前模式的获取、模式的切换等是比较简单的,可以自行尝试下!

THE END

好了,path的基本介绍暂时这样了,其实还有两个有关于贝塞尔曲线的方法是很重要的,但是我自己也搞得云里雾里的,后期研究得有点心得在补充吧!






猜你喜欢

转载自blog.csdn.net/MonaLisaTearr/article/details/79571454