Canvas&Paint 知识梳理(6) - 绘制路线 Path 基本用法

一、概述

在实际的开发当中,我们经常会涉及到绘制路径,这里我们总结一下Path的常用API

二、基本用法

对于一个Path来说,它其中有很多的”子路径“,对于每个”子路径“,它又会有两个变量,"源点"和"当前点",也就是源码当中所描述的:

The Path class encapsulates compound (multiple contour) geometric paths

2.1 简单连线 - xxxTo

下面是Path当中和连线相关的方法:

public void moveTo(float x, float y)
public void rMoveTo(float dx, float dy)

每次调用完moveTo/rMoveTo方法,都会生成一条新的子路径,这两者的区别在于:
1.新建一条路径,(x, y)作为这个新路径的“源点"的值,在图上不会产生新的连线。
2.相对于当前路径的"当前点"的值,移动坐标(dx, dy),作为新路径的"源点",在图上不会产生新的连线。

public void lineTo(float x, float y)
public void rLineTo(float dx, float dy)

1.从当前点位置,直线连接到绝对坐标(x, y),如果没有调用过moveTo/rMoveTo,那么会先调用moveTo(0, 0)来生成一条从源点开始的子路径。
2.相对于当前点坐标,移动(dx, dy)作为终点坐标,并连接起点和终点。

public void quadTo(float x1, float y1, float x2, float y2)
public void rQuadTo(float dx1, float dy1, float dx2, float dy2)

1.从当前点位置开始,以(x1, y1)为控制点,按一阶贝塞尔曲线计算方法连接到(x2, y2),如果之前没有调用过moveTo/rMoveTo,也会先调用一次moveTo(0, 0)方法。
2.类似于上面,只不过终点坐标是相对于当前点坐标移动了(dx2, dy2)后的值。

public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
public void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)

和一阶贝塞尔曲线类似,不过是多了一个控制点(x2, y2)

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

上面这三个函数,最终都是调用了同一个方法:

native_arcTo(mNativePath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo)

它的原理就是:首先根据RectF或者4个点的坐标来确定一个矩形区域,之后得到这个矩形的内切椭圆,然后再根据startAnglesweepAngle来确定这个内切源的一段弧,这段弧就是新绘制的路径。但是这段弧的起点很有可能和Path的当前点不是重合的,这时候就根据forceMoveTo来决定是否产生一条新的子路径,从最终的结果看,就是是否需要绘制一条从Path当前点到这段弧起点的连线,如果为forceMoveTo=false,那么就绘制这么一条直线,反之,则不绘制,forceMoveTo的默认值为false
forceMoveTo=false

    private void drawArcTo(Canvas canvas) {
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        Path path = new Path();
        path.moveTo(0, 300);
        path.lineTo(300, 300);
        path.arcTo(0, 0, 500, 500, 0, -90, false);
        path.rLineTo(0, 250);
        canvas.drawPath(path, mPaint);
    }

forceMoveTo=true

    private void drawArcTo(Canvas canvas) {
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        Path path = new Path();
        path.moveTo(0, 300);
        path.lineTo(300, 300);
        path.arcTo(0, 0, 500, 500, 0, -90, true);
        path.rLineTo(0, 250);
        canvas.drawPath(path, mPaint);
    }

forceMoveTo=true + path.close()

    private void drawArcTo(Canvas canvas) {
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        Path path = new Path();
        path.moveTo(0, 300);
        path.lineTo(300, 300);
        path.arcTo(0, 0, 500, 500, 0, -90, true);
        path.rLineTo(0, 250);
        path.close();
        canvas.drawPath(path, mPaint);
    }

对于这种情况,由于采用了true标志位,因此生成了一条新的”子路径“,所以在调用close方法之后,连接的是当前子路径的源点和当前点。

public void close()

如果对于当前”子路径“来说,它的"当前点"和"源点"不重合,那么会绘制一条从当前点到源点的直线。

2.2 直接增加新的路径

除了采用连线的方法来确定一条路径,Path还提供了addXXX来直接地增加一整段的路径,下面是相关的方法:


效果其实从函数名上就可以很清楚地看出来,当调用完addXXX方法之后,会增加一条子路径到Path当中,并把该子路径作为Path的当前子路径。

    private void drawAddArc(Canvas canvas) {
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        Path path = new Path();
        path.moveTo(0, 250);
        path.lineTo(500, 250);
        path.addArc(0, 0, 500, 500, 0, -90);
        path.close();
        canvas.drawPath(path, mPaint);
    }

下面是运行的结果:


2.3 填充类型FillType

关于填充类型的方法有如下这些:


我们先来看一下FillType的定义有哪些:

    public enum FillType {
        // these must match the values in SkPath.h
        /**
         * Specifies that "inside" is computed by a non-zero sum of signed
         * edge crossings.
         */
        WINDING         (0),
        /**
         * Specifies that "inside" is computed by an odd number of edge
         * crossings.
         */
        EVEN_ODD        (1),
        /**
         * Same as {@link #WINDING}, but draws outside of the path, rather than inside.
         */
        INVERSE_WINDING (2),
        /**
         * Same as {@link #EVEN_ODD}, but draws outside of the path, rather than inside.
         */
        INVERSE_EVEN_ODD(3);

        FillType(int ni) {
            nativeInt = ni;
        }

        final int nativeInt;
    }

关于这个FillType的理解,下面这篇文章的作者说的很好:

http://blog.csdn.net/u013831257/article/details/51477575

我这里只是稍微地总结一下:

2.3.1 FillType的意义

我们都知道Paint有三种模式:FILL/FILL_AND_STROKE/STROKE,对于STROKE来说,是只绘制轮廓,而两种模式都涉及到”填充“,那么”填充“就涉及到怎么定义一个Path所组成的图形的内部,FillType就是用来确定这个所谓的”内部“的定义的,需要注意,只讨论封闭图形的情况。

2.3.2 FillType的类型的含义

  • EVEN_ODD表示奇偶规则:奇数表示在图形内,偶数表示在图形外,并绘制内部。
    从任意位置p作一条射线, 若与该射线相交的图形边的数目为奇数,则p是图形内部点,否则是外部点。
  • INVERSE_EVEN_ODD:和EVEN_ODD对应,绘制外部。
  • WINDING表示非零环绕数规则:若环绕数为0表示在图形外,非零表示在图形内,并绘制内部。
    首先使图形的边变为矢量。将环绕数初始化为零。再从任意位置p作一条射线。当从p点沿射线方向移动时,对在每个方向上穿过射线的边计数,每当图形的边从右到左穿过射线时,环绕数加1,从左到右时,环绕数减1。处理完图形的所有相关边之后,若环绕数为非零,则p为内部点,否则,p是外部点。
  • INVERSE_WINDING:和WINDING对应,绘制外部。

2.3.3 示例

    private void drawFillType(Canvas canvas) {
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        Path fillTypePath = new Path();
        //两个圆圈都为顺时针的情况.
        fillTypePath.addCircle(250, 250, 250, Path.Direction.CW);
        fillTypePath.addCircle(500, 500, 250, Path.Direction.CW);
        //填充类型采用奇偶原则.
        fillTypePath.setFillType(Path.FillType.EVEN_ODD);
        canvas.drawPath(fillTypePath, mPaint);
    }
    private void drawFillType(Canvas canvas) {
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        Path fillTypePath = new Path();
        //两个圆圈都为顺时针的情况.
        fillTypePath.addCircle(250, 250, 250, Path.Direction.CW);
        fillTypePath.addCircle(500, 500, 250, Path.Direction.CW);
        //填充类型采用非零环绕数规则.
        fillTypePath.setFillType(Path.FillType.WINDING);
        canvas.drawPath(fillTypePath, mPaint);
    }
    private void drawFillType(Canvas canvas) {
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        Path fillTypePath = new Path();
        //第一个圆圈为顺时针,第二个圆圈为逆时针的情况.
        fillTypePath.addCircle(250, 250, 250, Path.Direction.CW);
        fillTypePath.addCircle(500, 500, 250, Path.Direction.CCW);
        //填充类型采用奇偶原则.
        fillTypePath.setFillType(Path.FillType.EVEN_ODD);
        canvas.drawPath(fillTypePath, mPaint);
    }
    private void drawFillType(Canvas canvas) {
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        Path fillTypePath = new Path();
        //第一个圆圈为顺时针,第二个圆圈为逆时针的情况.
        fillTypePath.addCircle(250, 250, 250, Path.Direction.CW);
        fillTypePath.addCircle(500, 500, 250, Path.Direction.CCW);
        //填充类型采用非零环绕数规则.
        fillTypePath.setFillType(Path.FillType.WINDING);
        canvas.drawPath(fillTypePath, mPaint);
    }


可以看到,对于24,由于Path.FillType.WINDING会涉及到路径的方向,因此路径方向不同会影响它的结果,但是对比13,由于EVEN_ODD的判断只涉及到边,不涉及到路径的方向,因此不会影响它的结果。

2.4 Path的计算

关于Path的计算,有下面这两个方法:


其中,第一个表示对当前Path和参数中的Path进行计算,计算结果放入当前Path当中;第二个表示对参数内的两个Path进行计算,计算的结果放入到当前Path中,计算的类型有以下几种:

    public enum Op {
        /**
         * Subtract the second path from the first path.
         */
        DIFFERENCE,
        /**
         * Intersect the two paths.
         */
        INTERSECT,
        /**
         * Union (inclusive-or) the two paths.
         */
        UNION,
        /**
         * Exclusive-or the two paths.
         */
        XOR,
        /**
         * Subtract the first path from the second path.
         */
        REVERSE_DIFFERENCE
    }
  • DIFFERENCE:从Path1中减去Path2
  • INTERSECT:取Path1Path2的交集。
  • UNION:取Path1Path2的并集。
  • XOR:从Path1Path2的并集中,减去它们的交集。
  • REVERSE_DIFFERENCE:从Path2中减去Path1

我们以XOR为例子:

    private void drawOp(Canvas canvas) {
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        Path path1 = new Path();
        //Path1为顺时针.
        path1.addCircle(250, 250, 250, Path.Direction.CW);
        Path path2 = new Path();
        //Path2为逆时针
        path2.addCircle(400, 250, 250, Path.Direction.CCW);
        path1.op(path2, Path.Op.XOR);
        canvas.drawPath(path1, mPaint);
    }

最后的结果为:


2.5 重置方法

Path提供了两种重置方法:

    /**
     * Clear any lines and curves from the path, making it empty.
     * This does NOT change the fill-type setting.
     */
    public void reset()

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

推荐阅读更多精彩内容

  • 最近项目中要实现加速球效果。是时候该学习一波了,好了废话不多说,记笔记,还是从自己发憷的自定义view开始。 先来...
    laifrog阅读 1,462评论 0 4
  • 版权声明:本文为博主原创文章,未经博主允许不得转载。系列教程:Android开发之从零开始系列大家要是看到有错误的...
    Anlia阅读 20,594评论 1 24
  • 一、Path常用方法表 为了兼容性(偷懒) 本表格中去除了部分API21(即安卓版本5.0)以上才添加的方法。 二...
    吕侯爷阅读 680评论 0 3
  • UIBezierPath Class Reference 译:UIBezierPath类封装了Core Graph...
    鋼鉄侠阅读 1,717评论 0 3
  • 第一夜 雷雨交加的夜,天黑得好像上帝讲了个冷笑话。 半夜三点,周藤目不转睛地盯着那个窗外的人影。漆黑的影子像僵尸一...
    留言飞语阅读 713评论 2 2