Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 安卓自定義View進階 - Path

安卓自定義View進階 - Path

編輯:關於Android編程

 

一.Path常用方法表

為了兼容性(偷懶) 本表格中去除了在API21(即安卓版本5.0)以上才添加的方法。忍不住吐槽一下,為啥看起來有些順手就能寫的重載方法要等到API21才添加上啊。寶寶此刻內心也是崩潰的。

作用 相關方法 備注 移動起點 moveTo 移動下一次操作的起點位置 設置終點 setLastPoint 重置當前path中最後一個點位置,如果在繪制之前調用,效果和moveTo相同 連接直線 lineTo 添加上一個點到當前點之間的直線到Path 閉合路徑 close 連接第一個點連接到最後一個點,形成一個閉合區域 添加內容 addRect, addRoundRect, addOval, addCircle, addPath, addArc, arcTo 添加(矩形, 圓角矩形, 橢圓, 圓, 路徑, 圓弧) 到當前Path (注意addArc和arcTo的區別) 是否為空 isEmpty 判斷Path是否為空 是否為矩形 isRect 判斷path是否是一個矩形 替換路徑 set 用新的路徑替換到當前路徑所有內容 偏移路徑 offset 對當前路徑之前的操作進行偏移(不會影響之後的操作) 貝塞爾曲線 quadTo, cubicTo 分別為二次和三次貝塞爾曲線的方法 rXxx方法 rMoveTo, rLineTo, rQuadTo, rCubicTo 不帶r的方法是基於原點的坐標系(偏移量),rXxx方法是基於當前點坐標系(偏移量) 填充模式 setFillType, getFillType, isInverseFillType, toggleInverseFillType 設置,獲取,判斷和切換填充模式 提示方法 incReserve 提示Path還有多少個點等待加入(這個方法貌似會讓Path優化存儲結構) 布爾操作(API19) op 對兩個Path進行布爾運算(即取交集、並集等操作) 計算邊界 computeBounds 計算Path的邊界 重置路徑 reset, rewind 清除Path中的內容
reset不保留內部數據結構,但會保留FillType.
rewind會保留內部的數據結構,但不保留FillType 矩陣操作 transform 矩陣變換

二、Path方法詳解

rXxx方法

此類方法可以看到和前面的一些方法看起來很像,只是在前面多了一個r,那麼這個rXxx和前面的一些方法有什麼區別呢?

rXxx方法的坐標使用的是相對位置(基於當前點的位移),而之前方法的坐標是絕對位置(基於當前坐標系的坐標)。

舉個例子:

    Path path = new Path();

    path.moveTo(100,100);
    path.lineTo(100,200);

    canvas.drawPath(path,mDeafultPaint);

\

在這個例子中,先移動點到坐標(100,100)處,之後再連接 點(100,100)(100,200) 之間點直線,非常簡單,畫出來就是一條豎直的線,那接下來看下一個例子:

    Path path = new Path();

    path.moveTo(100,100);
    path.rLineTo(100,200);

    canvas.drawPath(path,mDeafultPaint);

\

這個例子中,將 lineTo 換成了 rLineTo 可以看到在屏幕上原本是豎直的線變成了傾斜的線。這是因為最終我們連接的是 (100,100)(200, 300) 之間的線段。

在使用rLineTo之前,當前點的位置在 (100,100) , 使用了 rLineTo(100,200) 之後,下一個點的位置是在當前點的基礎上加上偏移量得到的,即 (100+100, 100+200) 這個位置,故最終結果如上所示。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxzdHJvbmc+UFM6ILTLtKa99tLUIHJMaW5lVG8gzqrA/aOs1rvSqsDtveIgJmxkcXVvO774ttTX+LHqJnJkcXVvOyC6zSAmbGRxdW87z+C21Nf4seomcmRxdW87ILXEx/ix8KOsxuTL+7e9t6jA4LHIvLS/yaGjPC9zdHJvbmc+PC9wPg0KPGgzIGlkPQ=="填充模式">填充模式

我們在之前的文章中了解到,Paint有三種樣式,“描邊” “填充” 以及 “描邊加填充”,我們這裡所了解到就是在Paint設置為後兩種樣式時不同的填充模式對圖形渲染效果的影響

我們要給一個圖形內部填充顏色,首先需要分清哪一部分是外部,哪一部分是內部,機器不像我們人那麼聰明,機器是如何判斷內外呢?

機器判斷圖形內外,一般有以下兩種方法:

PS:此處所有的圖形均為封閉圖形,不包括圖形不封閉這種情況。

方法 判定條件 解釋 奇偶規則 奇數表示在圖形內,偶數表示在圖形外 從任意位置p作一條射線, 若與該射線相交的圖形邊的數目為奇數,則p是圖形內部點,否則是外部點。 非零環繞數規則 若環繞數為0表示在圖形內,非零表示在圖形外 首先使圖形的邊變為矢量。將環繞數初始化為零。再從任意位置p作一條射線。當從p點沿射線方向移動時,對在每個方向上穿過射線的邊計數,每當圖形的邊從右到左穿過射線時,環繞數加1,從左到右時,環繞數減1。處理完圖形的所有相關邊之後,若環繞數為非零,則p為內部點,否則,p是外部點。

接下來我們先了解一下兩種判斷方法是如何工作的。

奇偶規則(Even-Odd Rule)

這一個比較簡單,也容易理解,直接用一個簡單示例來說明。

\

在上圖中有一個四邊形,我們選取了三個點來判斷這些點是否在圖形內部。

>
P1: 從P1發出一條射線,發現圖形與該射線相交邊數為0,偶數,故P1點在圖形外部。

P2: 從P2發出一條射線,發現圖形與該射線相交邊數為1,奇數,故P2點在圖形內部。

P3: 從P3發出一條射線,發現圖形與該射線相交邊數為2,偶數,故P3點在圖形外部。

非零環繞數規則(Non-Zero Winding Number Rule)

非零環繞數規則相對來說比較難以理解一點。

我們在之前的文章 AndroidNote/blob/master/CustomView/Advance/%5B5%5DPath_Basic.md">Path之基本操作 中我們了解到,在給Path中添加圖形時需要指定圖形的添加方式,是用順時針還是逆時針,另外我們不論是使用lineTo,quadTo,cubicTo還是其他連接線的方法,都是從一個點連接到另一個點,換言之,Path中任何線段都是有方向性的,這也是使用非零環繞數規則的基礎。

我們依舊用一個簡單的例子來說明非零環繞數規則的用法:

PS: 注意圖形中線段的方向性!

\

>
P1: 從P1點發出一條射線,沿射線防線移動,並沒有與邊相交點部分,環繞數為0,故P1在圖形外邊。

P2: 從P2點發出一條射線,沿射線方向移動,與圖形點左側邊相交,該邊從左到右穿過穿過射線,環繞數-1,最終環繞數為-1,故P2在圖形內部。

P3: 從P3點發出一條射線,沿射線方向移動,在第一個交點處,底邊從右到左穿過射線,環繞數+1,在第二個交點處,右側邊從左到右穿過射線,環繞數-1,最終環繞數為0,故P3在圖形外部。

通常,這兩種方法的判斷結果是相同的,但也存在兩種方法判斷結果不同的情況,如下面這種情況:

注意圖形線段的方向,就不詳細解釋了,用上面的方法進行判斷即可。

自相交圖形

自相交圖形定義:多邊形在平面內除頂點外還有其他公共點。

簡單的提一下自相交圖形,了解概念即可,下圖就是一個簡單的自相交圖形:

\

Android中的填充模式

Android中的填充模式有四種,是封裝在Path中的一個枚舉。

模式 簡介 EVEN_ODD 奇偶規則 INVERSE_EVEN_ODD 反奇偶規則 WINDING 非零環繞數規則 INVERSE_WINDING 反非零環繞數規則

我們可以看到上面有四種模式,分成兩對,例如 “奇偶規則” 與 “反奇偶規則” 是一對,它們之間有什麼關系呢?

Inverse 和含義是“相反,對立”,說明反奇偶規則剛好與奇偶規則相反,例如對於一個矩形而言,使用奇偶規則會填充矩形內部,而使用反奇偶規則會填充矩形外部,這個會在後面示例中代碼展示兩者對區別。

Android與填充模式相關的方法

這些都是Path中的方法。

方法 作用 setFillType 設置填充規則 getFillType 獲取當前填充規則 isInverseFillType 判斷是否是反向(INVERSE)規則 toggleInverseFillType 切換填充規則(即原有規則與反向規則之間相互切換)

示例演示:

本演示著重於幫助理解填充模式中的一些難點和易混淆的問題,對於一些比較簡單的問題,讀者可自行驗證,本文中不會過多贅述。

奇偶規則與反奇偶規則
    mDeafultPaint.setStyle(Paint.Style.FILL);                   // 設置畫布模式為填充

    canvas.translate(mViewWidth / 2, mViewHeight / 2);          // 移動畫布(坐標系)

    Path path = new Path();                                     // 創建Path

    //path.setFillType(Path.FillType.EVEN_ODD);                   // 設置Path填充模式為 奇偶規則
    path.setFillType(Path.FillType.INVERSE_WINDING);            // 反奇偶規則

    path.addRect(-200,-200,200,200, Path.Direction.CW);         // 給Path中添加一個矩形

下面兩張圖片分別是在奇偶規則於反奇偶規則的情況下繪制的結果,可以看出其填充的區域剛好相反:

PS: 白色為背景色,黑色為填充色。

\

圖形邊的方向對非零奇偶環繞數規則填充結果的影響

我們之前討論過給Path添加圖形時順時針與逆時針的作用,除了上次講述的方便記錄外,就是本文所涉及的另外一個重要作用了: “作為非零環繞數規則的判斷依據。”

通過前面我們已經大致了解了在圖形邊的方向會如何影響到填充效果,我們這裡驗證一下:

    mDeafultPaint.setStyle(Paint.Style.FILL);                   // 設置畫筆模式為填充

    canvas.translate(mViewWidth / 2, mViewHeight / 2);          // 移動畫布(坐系)

    Path path = new Path();                                     // 創建Path

    // 添加小正方形 (通過這兩行代碼來控制小正方形邊的方向,從而演示不同的效果)
    // path.addRect(-200, -200, 200, 200, Path.Direction.CW);
    path.addRect(-200, -200, 200, 200, Path.Direction.CCW);

    // 添加大正方形
    path.addRect(-400, -400, 400, 400, Path.Direction.CCW);

    path.setFillType(Path.FillType.WINDING);                    // 設置Path填充模式為非零環繞規則

    canvas.drawPath(path, mDeafultPaint);                       // 繪制Path

\

布爾操作(API19)

布爾操作與我們中學所學的集合操作非常像,只要知道集合操作中等交集,並集,差集等操作,那麼理解布爾操作也是很容易的。

布爾操作是兩個Path之間的運算,主要作用是用一些簡單的圖形通過一些規則合成一些相對比較復雜,或難以直接得到的圖形

如太極中的陰陽魚,如果用貝塞爾曲線制作的話,可能需要六段貝塞爾曲線才行,而在這裡我們可以用四個Path通過布爾運算得到,而且會相對來說更容易理解一點。

\

    canvas.translate(mViewWidth / 2, mViewHeight / 2);

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

    path1.addCircle(0, 0, 200, Path.Direction.CW);
    path2.addRect(0, -200, 200, 200, Path.Direction.CW);
    path3.addCircle(0, -100, 100, Path.Direction.CW);
    path4.addCircle(0, 100, 100, Path.Direction.CCW);


    path1.op(path2, Path.Op.DIFFERENCE);
    path1.op(path3, Path.Op.UNION);
    path1.op(path4, Path.Op.DIFFERENCE);

    canvas.drawPath(path1, mDeafultPaint);

前面演示了布爾運算的作用,接下來我們了解一下布爾運算的核心:布爾邏輯。

Path的布爾運算有五種邏輯,如下:

邏輯名稱 類比 說明 示意圖 DIFFERENCE 差集 Path1中減去Path2後剩下的部分 \ REVERSE_DIFFERENCE 差集 Path2中減去Path1後剩下的部分 INTERSECT 交集 Path1與Path2相交的部分 UNION 並集 包含全部Path1和Path2 XOR 異或 包含Path1與Path2但不包括兩者相交的部分

布爾運算方法

通過前面到理論知識鋪墊,相信大家對布爾運算已經有了基本的認識和理解,下面我們用代碼演示一下布爾運算:

在Path中的布爾運算有兩個方法

    boolean op (Path path, Path.Op op)
    boolean op (Path path1, Path path2, Path.Op op)

兩個方法中的返回值用於判斷布爾運算是否成功,它們使用方法如下:

``java
// 對 path1 和 path2 執行布爾運算,運算方式由第二個參數指定,運算結果存入到path1中。
path1.op(path2, Path.Op.DIFFERENCE);

// 對 path1 和 path2 執行布爾運算,運算方式由第三個參數指定,運算結果存入到path3中。
path3.op(path1, path2, Path.Op.DIFFERENCE)

#### 布爾運算示例

![](http://ww1.sinaimg.cn/large/005Xtdi2gw1f43jz8xnbxj308c0etwes.jpg)

代碼:

``` java
    int x = 80;
    int r = 100;

    canvas.translate(250,0);

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

    path1.addCircle(-x, 0, r, Path.Direction.CW);
    path2.addCircle(x, 0, r, Path.Direction.CW);

    pathOpResult.op(path1,path2, Path.Op.DIFFERENCE);
    canvas.translate(0, 200);
    canvas.drawText("DIFFERENCE", 240,0,mDeafultPaint);
    canvas.drawPath(pathOpResult,mDeafultPaint);

    pathOpResult.op(path1,path2, Path.Op.REVERSE_DIFFERENCE);
    canvas.translate(0, 300);
    canvas.drawText("REVERSE_DIFFERENCE", 240,0,mDeafultPaint);
    canvas.drawPath(pathOpResult,mDeafultPaint);

    pathOpResult.op(path1,path2, Path.Op.INTERSECT);
    canvas.translate(0, 300);
    canvas.drawText("INTERSECT", 240,0,mDeafultPaint);
    canvas.drawPath(pathOpResult,mDeafultPaint);

    pathOpResult.op(path1,path2, Path.Op.UNION);
    canvas.translate(0, 300);
    canvas.drawText("UNION", 240,0,mDeafultPaint);
    canvas.drawPath(pathOpResult,mDeafultPaint);

    pathOpResult.op(path1,path2, Path.Op.XOR);
    canvas.translate(0, 300);
    canvas.drawText("XOR", 240,0,mDeafultPaint);
    canvas.drawPath(pathOpResult,mDeafultPaint);





計算邊界

這個方法主要作用是計算Path所占用的空間以及所在位置,方法如下:

    void computeBounds (RectF bounds, boolean exact)





它有兩個參數:

參數 作用 bounds 測量結果會放入這個矩形 exact 是否精確測量,目前這一個參數作用已經廢棄,一般寫true即可。

關於exact如有疑問可參見Google官方的提交記錄Path.computeBounds()

計算邊界示例

計算path邊界的一個簡單示例.

\

代碼:

    // 移動canvas,mViewWidth與mViewHeight在 onSizeChanged 方法中獲得
    canvas.translate(mViewWidth/2,mViewHeight/2);

    RectF rect1 = new RectF();              // 存放測量結果的矩形

    Path path = new Path();                 // 創建Path並添加一些內容
    path.lineTo(100,-50);
    path.lineTo(100,50);
    path.close();
    path.addCircle(-100,0,100, Path.Direction.CW);

    path.computeBounds(rect1,true);         // 測量Path

    canvas.drawPath(path,mDeafultPaint);    // 繪制Path

    mDeafultPaint.setStyle(Paint.Style.STROKE);
    mDeafultPaint.setColor(Color.RED);
    canvas.drawRect(rect1,mDeafultPaint);   // 繪制邊界

重置路徑

重置Path有兩個方法,分別是reset和rewind,兩者區別主要有一下兩點:

方法 是否保留FillType設置 是否保留原有數據結構 reset 是 否 rewind 否 是

這個兩個方法應該何時選擇呢?

選擇權重: FillType > 數據結構

因為“FillType”影響的是顯示效果,而“數據結構”影響的是重建速度。

總結

Path中常用的方法到此已經結束,希望能夠幫助大家加深對Path對理解運用,讓大家能夠用Path愉快的玩耍。( ̄▽ ̄)

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved