Graphics2D API:Path基本操作

Path是一个类,表示“路径”.
路径:就是无数个点连接起来形成的轨迹,这个路径中还包括起点信息、终点信息,方向信息

Canvas中绘制路径Path的方法:

public void drawPath(Path path, Paint paint)

一、Path方法大全

这里先给出Path中常用方法,现在看不懂没关系,在后面都会一一介绍到

1、移动起点
moveTo、rMoveTo 移动画笔下一次操作的起点
2、设置终点
setLastPoint  重置当前Path中最后一个点位置,会影响之前的绘制效果 
3、两点之间连接成一条线段
lineTo、rLineTo  将两点之间连接成一条线段
close 将最开始操作点、最后操作点连接起来,形成闭合路径
4、添加基本图形的路径
addRect、addRoundRect添加直角、圆角矩形
addOval、addCircle、addArc、arcTo添加椭圆、圆、弧1、弧2
5、判断
isEmpty 判断Path是否为空
isRect 判断path是否是一个矩形
6、设置路径,偏移路径,添加路径
set 为当前Path设置路径
offset 对当前路径进行偏移(不会影响之后的操作)
addPath  为当前Path添加路径
7、填充模式
setFillType  设置填充模式
getFillType  获取当前填充模式
isInverseFillType  判断是否是反向填充模式
toggleInverseFillType  原有规则与反向规则之间相互切换
8、重置
reset  保留填充模式重置path
rewind  保留数据结构重置path
9、贝塞尔曲线
quadTo、rQuadTo  二阶贝赛尔曲线
cubicTo、rCubicTo  三阶贝塞尔曲线
10、两个Path运算(差集、反差集、交集、并集、补集)
op 两个Path进行运算
11、计算边界
computeBounds 计算Path的边界信息保存在矩形RectF对象中
12、矩阵操作
transform

二、Path基础

1、移动起点

现实中画画肯定有个起始点,Path添加路径也有个起始点

public void moveTo(float x, float y)
绝对定位------将画笔移动到点(x,y)

public void rMoveTo(float dx, float dy)
r:relative
相对定位------将画笔移动到点(x+dx,y+dy),(x,y)是上一次的点

现实中作画时肯定有个开始点,这两个方法就是Path中确定开始点的
默认开始点(0,0)
现实中作画时肯定会收笔,就会出现一个终点,在Path中每次添加完路径后的终点就是下一次添加路径的起始点
2、开放路径、闭合路径

从起点开始在路径中添加线段,一直到终点停止,此时就形成了一个开放路径,若把终点和起点连接起来(闭合),或者线段最后的终点和起点重合,就会形成一个闭合路径.

3、判断一个点在图形内还是图形外
(1)奇偶规则

从点P任意方向作射线,若射线与图形交点为奇数,则点P在图形内,交点为偶数表示点P在图形外

奇偶规则

P1、P2作出的射线与图形的交点分别为0、2,偶数,故在图形外部
P3作出的射线与图形的交点分别为1,奇数,故在图形内部

(2)非零环绕数规则

首先将多边形的边矢量化,从点P任意方向作射线,每当图形的边从射线右侧穿到射线左侧时,环绕数+1,从左到右穿过时,环绕数-1。最终环绕数和为非零,则点P为内部点,否则,点P是外部点

非零环绕数规则

P1:不相交,所以环绕数为0,在图形外部
P2:矩形底边从射线右侧穿到射线左侧,环绕数+1,矩形右边从从射线左侧穿到射线右侧,环绕数-1,最终环绕数和为0,在图形内部
P3:矩形底边从射线右侧穿到射线左侧,环绕数+1,在图形内部

PS:这里的图形指的是封闭路径,通常情况下这两种判断方法结果是一样的,但是也会出现不一样的情况,在下面填充模式会介绍到

三、Path中添加线段

Path类可以添加各种形状的线条,并且能将线条组合在一起,闭合之后就成是一个多边形.

public void lineTo(float x, float y)
(x,y):直线的结束点,又是下一次绘制直线路径的开始点
在上一个点与当前新点(x,y)之间画一条线
(如果没有定义过初始点并且没有上一次操作,起始点就是坐标原点(0,0))

public void rLineTo(float dx, float dy)
(dx+x,dy+y):直线的结束点,又是下一次绘制直线路径的开始点
在上一个点与当前新点(dx+x,dy+y)之间画一条线

public void close()
在第一个点和最后一个点之间画一条线,形成闭合图形

测试:

    private void gogogo(Canvas canvas) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.BLUE);

        Path path = new Path();
        path.moveTo(50, 50);//起点:(50,50)
        path.lineTo(250, 50);//(50,50)到(250,50)之间画一条线,(250,50)是下一次绘制的起点
        path.lineTo(250, 250);//(250,50)到(250,250)之间画一条线
        path.close();//闭合
        canvas.drawPath(path, mPaint);


        mPaint.setColor(Color.RED);
        Path path2 = new Path();
        path2.moveTo(300, 50);//起点:(300,50)
        path2.lineTo(500, 50);//(300,50)到(500,50)之间画一条线,(500,50)是下一次绘制的起点
        path2.rMoveTo(-200, 0);//将起点移动到(500-200,50+0)---(300,,50)成为下一次绘制的起点
        path2.rLineTo(0, 200);//(300,50)到(300+0,50+200)之间画一条线
        canvas.drawPath(path2, mPaint);
    }

四、setLastPoint

Path中还有一个移动操作点位置的方法,和moveTo有区别:

public void setLastPoint(float dx, float dy)
设置Path之前操作的最后一个点位置

拿上面蓝色三角形举例:

    private void gogogo(Canvas canvas) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.BLUE);

        Path path = new Path();
        path.moveTo(50, 50);
        path.lineTo(250, 50);
        path.lineTo(250, 250);
        path.setLastPoint(250,100);//将最后一个点位置设置为(250,100)
        path.close();
        canvas.drawPath(path, mPaint);
    }

仅仅多加了一行代码:本来闭合前最后一个点应该是(250, 250),现在变成了(250,100),所以闭合时连接的是(250,100)和最初的起点(50, 50).

可见:setLastPoint会影响之前的绘图,还会把之后的绘图起点更改
moveTo仅仅影响之后的绘图,对之前的绘图没有影响.

五、Path中添加矩形

往Path中添加矩形、椭圆、圆、弧,需要调用Path类中以“add”开头的相关方法.
关于矩形,请看Rect类、RectF类,Canvas中绘制矩形相关介绍.

1、直角矩形
public void addRect(RectF rect, Direction dir)

public void addRect(float left, float top, float right, float bottom, Direction dir)

往Path中添加一个直角矩形
Direction:Path类中枚举类,表示Path绘制的方向,可选值:
            Path.Direction.CW  顺时针
            Path.Direction.CCW  逆时针

方向:

path.addRect(100,100,300,200, Path.Direction.CW);//矩形ABCD
path.addRect(100,100,300,200, Path.Direction.CCW);//矩形ADCB
Path中的方向
2、圆角矩形

public void addRoundRect(RectF rect, float rx, float ry, Direction dir)
public void addRoundRect(float left, float top, float right, float bottom, float rx, float ry,Direction dir)
往Path中添加一个圆角矩形(4个圆角一样)


public void addRoundRect(RectF rect, float[] radii, Direction dir)
public void addRoundRect(float left, float top, float right, float bottom, float[] radii, Direction dir)
往Path中添加一个圆角矩形(4个圆角可定制)
这里的参数除了radii,之前的文章都介绍过
radii:定义4个圆角的数组,一个圆角需要2个值,总共需要8个值:
顺序:左上角,右上角,右下角,左下角 

测试:

    private void gogogo(Canvas canvas) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.BLUE);

        Path path = new Path();
        RectF rectF = new RectF(50, 50, 600, 400);
        path.addRect(rectF, Path.Direction.CW);
        RectF rectF2 = new RectF(100, 100, 500, 300);
        path.addRoundRect(rectF2, new float[]{10, 10, 20, 20, 0, 0, 40, 40}, Path.Direction.CW);
        canvas.drawPath(path, mPaint);
    }

六、添加椭圆、圆、弧

关于圆,下面这些椭圆、圆、弧的方法参数和Canvas类中绘制圆作用一样,这里就不介绍了.

1、椭圆
public void addOval(RectF oval, Direction dir)

public void addOval(float left, float top, float right, float bottom, Direction dir)
2、圆
public void addCircle(float x, float y, float radius, Direction dir)
椭圆、圆的绘制方向
3、弧1
public void addArc(RectF oval, float startAngle, float sweepAngle)

public void addArc(float left, float top, float right, float bottom, float startAngle,
            float sweepAngle)
4、弧2
public void arcTo(RectF oval, float startAngle, float sweepAngle)

public void arcTo(RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)
                      
public void arcTo(float left, float top, float right, float bottom, float startAngle,float sweepAngle, boolean forceMoveTo)

这里多了一个参数没有介绍过:forceMoveTo
forceMoveTo表示是否强制开始一个新的起点,默认false,会在绘制弧的时候把弧的起点和Path上一次终点连接起来

测试:

    private void gogogo(Canvas canvas) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.BLUE);

        Path path = new Path();
        path.addOval(new RectF(50, 50, 600, 400), Path.Direction.CW);//顺时针椭圆
        path.addCircle(150, 550, 100, Path.Direction.CW);//顺时针圆
        path.addArc(new RectF(80, 80, 630, 430), 0, 90);//弧1
        path.arcTo(new RectF(100, 100, 660, 460), 0, 90, false);//弧2
        canvas.drawPath(path, mPaint);
    }

七、判断

public boolean isEmpty()
判断路径是否为空,为空,返回true(Path中未添加任何线段或者曲线)
public boolean isRect(RectF rect)
判断路径是否为矩形,如果是,返回true,将矩形的信息存放进参数rect中,否则返回false,忽略rect

这两个方法很简单,就不再测试了

八、设置路径,偏移路径,添加路径

1、设置路径
public void set(Path src) 
将传入的Path路径src设置给当前的Path对象

测试:

    private void gogogo(Canvas canvas) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.BLUE);

        Path path1 = new Path();
        Path path2 = new Path();

        path1.lineTo(200, 200);
//        path2.set(path1);设置路径

        canvas.drawPath(path2, mPaint);
    }

上面的测试代码运行界面上将没有任何效果,因为path2中未添加任何内容,但是放开注释,为path2设置路径后将会出现一条从(0,0)到(200,200)的线段.


2、偏移路径
public void offset(float dx, float dy)
对当前路径进行偏移,dx、dy分别是Path水平、竖直方向的平移距离(不会影响之后的操作)

接着上面测试代码新加一行:

    private void gogogo(Canvas canvas) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.BLUE);

        Path path1 = new Path();
        Path path2 = new Path();

        path1.lineTo(200, 200);
        path2.set(path1);//设置路径
        path2.offset(300, 100);//偏移路径

        canvas.drawPath(path2, mPaint);
    }
3、添加路径
public void addPath(Path src)
将src的路径添加到当前Path对象中

public void addPath(Path src, float dx, float dy)
将src的路径水平、竖直方向分别偏移dx、dy距离后添加到当前Path对象中

继续上面的测试:

    private void gogogo1(Canvas canvas) {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.BLUE);

        Path path1 = new Path();
        Path path2 = new Path();

        path1.lineTo(200, 200);
        path2.set(path1);//设置路径
        path2.offset(300, 100);//偏移路径
        path2.addPath(path1);//添加路径
        path2.addPath(path1, 200, 100);//添加路径

        canvas.drawPath(path2, mPaint);
    }

九、填充模式

public void setFillType(FillType ft)
设置填充模式

FillType 可选值:
  Path.FillType.WINDING 非零环绕数规则(默认填充模式)
  Path.FillType.INVERSE_WINDING 反非零环绕数规则
  Path.FillType.EVEN_ODD  奇偶规则
  Path.FillType.INVERSE_EVEN_ODD 反奇偶规则

填充:将图形内部填满某种颜色
Path代表路径,本文一开始介绍了如何判断一个点在图形的内部还是外部,而填充就是将Path形成的图形内部所有点都填满某种颜色,几乎所有绘图软件中都有填充功能,下面以电脑上的画图软件示例:


注意:填充针对的是图形内部

1、填充模式分为两组

根据奇偶规则判断的填充模式、根据非零环绕数规则判断的填充模式.
每组的两个模式填充效果相反.

2、为什么会分为两组

这是因为在某些情况下这两组判断的结果不一样.
对于简单的封闭路径(路径无相交的现象),图形的外部和内部和很容易判断.
但对于一些复杂的封闭路径,图形的外部和内部根据不同模式判断结果不一样.
(如下图)


根据奇偶规则:交点数为2,点P在图形外部
根据非零环绕数规则:环绕数为-2,非零,点P在图形内部
所以根据这两组模式填充的结果就不一样了


3、测试
    private void gogogo(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.BLUE);

        //同向的两个圆
        Path path = new Path();
        path.addCircle(300, 300, 150, Path.Direction.CW);
        path.addCircle(400, 300, 150, Path.Direction.CW);
        path.setFillType(Path.FillType.WINDING);
//        path.setFillType(Path.FillType.INVERSE_WINDING);
//        path.setFillType(Path.FillType.EVEN_ODD);
//        path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
        canvas.drawPath(path, mPaint);
    }

    private void gogogo(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setStrokeWidth(5);
        mPaint.setColor(Color.BLUE);

        //反向的两个圆
        Path path = new Path();
        path.addCircle(300, 300, 150, Path.Direction.CW);
        path.addCircle(400, 300, 150, Path.Direction.CCW);
        path.setFillType(Path.FillType.WINDING);
//        path.setFillType(Path.FillType.INVERSE_WINDING);
//        path.setFillType(Path.FillType.EVEN_ODD);
//        path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
        canvas.drawPath(path, mPaint);
    }
同向圆
反向圆
4、分析


上面测试了同向圆、反向圆形成图形的填充效果,可以看出:
(1)、Path的方向对奇偶规则没啥影响,因为不管你正向还是逆向,交点数不会改变,所以两组测试中根据奇偶规则判断的填充效果一致
(2)、Path的方向直接影响非零环绕数规则,同向时,两个圆相交区域内的点环绕数为-2,非零,所以在图形内部,反向时,两个圆相交区域内的点环绕数为0,在图形外部.

5、关于填充模式的其它几个方法
public FillType getFillType()  
获取当前的填充模式

public boolean isInverseFillType()  
判断当前填充模式是否是反向规则 (也就是判断是不是INVERSE_WINDING 、INVERSE_EVEN_ODD)

public void toggleInverseFillType()
切换填充规则 (即原有规则与反向规则之间相互切换)

十、重置

public void reset()
public void rewind()

都是重置Path
reset会保留填充模式 (FillType)
rewind不保留填充模式,而是保留Path内部内部数据结构
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容