天天看點

Android自定義系列——9.Path詳細用法

rXxx方法

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

Path path = new Path();

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

canvas.drawPath(path,mDeafultPaint);           

複制

Android自定義系列——9.Path詳細用法

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

Path path = new Path();

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

canvas.drawPath(path,mDeafultPaint);           

複制

Android自定義系列——9.Path詳細用法

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

在使用rLineTo之前,目前點的位置在 (100,100) , 使用了 rLineTo(100,200) 之後,下一個點的位置是在目前點的基礎上加上偏移量得到的,即 (100+100, 100+200) 這個位置。

此處僅以 rLineTo 為例,隻要了解 “絕對坐标” 和 “相對坐标” 的差別,其他方法類比即可。

填充模式

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

我們要給一個圖形内部填充顔色,首先需要厘清哪一部分是外部,哪一部分是内部,機器判斷圖形内外,一般有以下兩種方法:(此處所有的圖形均為封閉圖形,不包括圖形不封閉這種情況。)

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

奇偶規則(Even-Odd Rule)

Android自定義系列——9.Path詳細用法

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

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

複制

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

Path中添加圖形時需要指定圖形的添加方式,是用順時針還是逆時針,另外我們不論是使用lineTo,quadTo,cubicTo還是其他連接配接線的方法,都是從一個點連接配接到另一個點,換言之,Path中任何線段都是有方向性的,這也是使用非零環繞數規則的基礎。

Android自定義系列——9.Path詳細用法

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

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

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

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

Android自定義系列——9.Path詳細用法

自相交圖形

自相交圖形定義:多邊形在平面内除頂點外還有其他公共點。下圖就是一個簡單的自相交圖形:

Android自定義系列——9.Path詳細用法

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中添加一個矩形           

複制

下面兩張圖檔分别是在奇偶規則于反奇偶規則的情況下繪制的結果,可以看出其填充的區域剛好相反:(白色為背景色,黑色為填充色)

Android自定義系列——9.Path詳細用法
Android自定義系列——9.Path詳細用法

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

我們之前讨論過給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           

複制

Android自定義系列——9.Path詳細用法
Android自定義系列——9.Path詳細用法

布爾操作(API19)

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

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

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

Android自定義系列——9.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的布爾運算有五種邏輯,如下:

Android自定義系列——9.Path詳細用法

布爾運算方法

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

計算邊界示例

Android自定義系列——9.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”影響的是顯示效果,而“資料結構”影響的是重建速度。