1. 程式人生 > >安卓自定義View進階-Path之完結篇

安卓自定義View進階-Path之完結篇

經歷過前兩篇 Path之基本操作 和 Path之貝塞爾曲線 的講解,本篇終於進入Path的收尾篇,本篇結束後Path的大部分相關方法都已經講解完了,但Path還有一些更有意思的玩法,應該會在後續的文章中出現。

一.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) 這個位置,故最終結果如上所示。

PS: 此處僅以 rLineTo 為例,只要理解 “絕對座標” 和 “相對座標” 的區別,其他方法類比即可。

填充模式

我們在之前的文章中瞭解到,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)

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

我們在之前的文章 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_EVEN_ODD);            // 反奇偶規則

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)

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

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

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

布林運算示例

程式碼:

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即可。

計算邊界示例

計算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愉快的玩耍。( ̄▽ ̄)