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():清除路徑,但保留信息。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 最近項目中要實現加速球效果。是時候該學習一波了,好了廢話不多說,記筆記,還是從自己發憷的自定義view開始。 先來...
    laifrog閱讀 1,505評論 0 4
  • 版權聲明:本文為博主原創文章,未經博主允許不得轉載。系列教程:Android開發之從零開始系列大家要是看到有錯誤的...
    Anlia閱讀 20,867評論 1 24
  • 一、Path常用方法表 為了兼容性(偷懶) 本表格中去除了部分API21(即安卓版本5.0)以上才添加的方法。 二...
    呂侯爺閱讀 724評論 0 3
  • UIBezierPath Class Reference 譯:UIBezierPath類封裝了Core Graph...
    鋼鉄俠閱讀 1,781評論 0 3
  • 第一夜 雷雨交加的夜,天黑得好像上帝講了個冷笑話。 半夜三點,周藤目不轉睛地盯著那個窗外的人影。漆黑的影子像僵尸一...
    留言飛語閱讀 735評論 2 2