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

1、概述

在實際的開發當中,咱們常常會涉及到繪製路徑,這裏咱們總結一下Path的經常使用APIcanvas

2、基本用法

對於一個Path來講,它其中有不少的」子路徑「,對於每一個」子路徑「,它又會有兩個變量,"源點"和"當前點",也就是源碼當中所描述的:bash

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

2.1 簡單連線 - xxxTo

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

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)做爲終點座標,並鏈接起點和終點。ui

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)後的值。spa

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).net

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)
複製代碼

上面這三個函數,最終都是調用了同一個方法:code

native_arcTo(mNativePath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo)
複製代碼

它的原理就是:首先根據RectF或者4個點的座標來肯定一個矩形區域,以後獲得這個矩形的內切橢圓,而後再根據startAnglesweepAngle來肯定這個內切源的一段弧,這段弧就是新繪製的路徑。可是這段弧的起點頗有可能和Path的當前點不是重合的,這時候就根據forceMoveTo來決定是否產生一條新的子路徑,從最終的結果看,就是是否須要繪製一條從Path當前點到這段弧起點的連線,若是爲forceMoveTo=false,那麼就繪製這麼一條直線,反之,則不繪製,forceMoveTo的默認值爲falseforceMoveTo=falseblog

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=trueip

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():清除路徑,但保留信息。
相關文章
相關標籤/搜索