Canvas&Paint 知識梳理(6) 繪製路線 Path 基本用法

澤毛發表於2017-12-21

一、概述

在實際的開發當中,我們經常會涉及到繪製路徑,這裡我們總結一下Path的常用API

二、基本用法

對於一個Path來說,它其中有很多的”子路徑“,對於每個”子路徑“,它又會有兩個變數,"源點"和"當前點",也就是原始碼當中所描述的:

The Path class encapsulates compound (multiple contour) geometric paths
複製程式碼

2.1 簡單連線 - xxxTo

下面是Path當中和連線相關的方法:

Canvas&Paint 知識梳理(6)   繪製路線 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的預設值為falseforceMoveTo=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);
    }
複製程式碼

Canvas&Paint 知識梳理(6)   繪製路線 Path 基本用法

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);
    }
複製程式碼

Canvas&Paint 知識梳理(6)   繪製路線 Path 基本用法

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方法之後,連線的是當前子路徑的源點和當前點。

Canvas&Paint 知識梳理(6)   繪製路線 Path 基本用法

public void close()
複製程式碼

如果對於當前”子路徑“來說,它的"當前點"和"源點"不重合,那麼會繪製一條從當前點到源點的直線。

2.2 直接增加新的路徑

除了採用連線的方法來確定一條路徑,Path還提供了addXXX來直接地增加一整段的路徑,下面是相關的方法:

Canvas&Paint 知識梳理(6)   繪製路線 Path 基本用法
效果其實從函式名上就可以很清楚地看出來,當呼叫完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);
    }
複製程式碼

下面是執行的結果:

Canvas&Paint 知識梳理(6)   繪製路線 Path 基本用法

2.3 填充型別FillType

關於填充型別的方法有如下這些:

Canvas&Paint 知識梳理(6)   繪製路線 Path 基本用法
我們先來看一下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);
    }
複製程式碼

Canvas&Paint 知識梳理(6)   繪製路線 Path 基本用法

    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);
    }
複製程式碼

Canvas&Paint 知識梳理(6)   繪製路線 Path 基本用法

    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);
    }
複製程式碼

Canvas&Paint 知識梳理(6)   繪製路線 Path 基本用法

    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);
    }
複製程式碼

Canvas&Paint 知識梳理(6)   繪製路線 Path 基本用法
可以看到,對於24,由於Path.FillType.WINDING會涉及到路徑的方向,因此路徑方向不同會影響它的結果,但是對比13,由於EVEN_ODD的判斷只涉及到邊,不涉及到路徑的方向,因此不會影響它的結果。

2.4 Path的計算

關於Path的計算,有下面這兩個方法:

Canvas&Paint 知識梳理(6)   繪製路線 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);
    }
複製程式碼

最後的結果為:

Canvas&Paint 知識梳理(6)   繪製路線 Path 基本用法

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():清除路徑,但保留資訊。

相關文章