安卓自定義View進階:Path之玩出花樣(PathMeasure)
PS:不要問我為什麼不講 PathEffect,因為這個方法在後面的Paint系列中。
先放一個圖鎮樓,省的下面無聊的內容把你們都嚇跑了Σ( ̄。 ̄ノ)ノ
Path & PathMeasure
顧名思義,PathMeasure是一個用來測量Path的類,主要有以下方法:
構造方法
方法名 | 釋義 |
---|---|
PathMeasure() | 建立一個空的PathMeasure |
PathMeasure(Path path, boolean forceClosed) | 建立 PathMeasure 並關聯一個指定的Path(Path需要已經建立完成)。 |
公共方法
返回值 | 方法名 | 釋義 |
---|---|---|
void | setPath(Path path, boolean forceClosed) | 關聯一個Path |
boolean | isClosed() | 是否閉合 |
float | getLength() | 獲取Path的長度 |
boolean | nextContour() | 跳轉到下一個輪廓 |
boolean | getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) | 擷取片段 |
boolean | getPosTan(float distance, float[] pos, float[] tan) | 獲取指定長度的位置座標及該點切線值 |
boolean | getMatrix(float distance, Matrix matrix, int flags) | 獲取指定長度的位置座標及該點Matrix |
PathMeasure的方法也不多,接下來我們就逐一的講解一下。
1.建構函式
建構函式有兩個。
無參建構函式:
1 | PathMeasure() |
用這個建構函式可建立一個空的 PathMeasure,但是使用之前需要先呼叫 setPath 方法來與 Path 進行關聯。被關聯的 Path 必須是已經建立好的,如果關聯之後 Path 內容進行了更改,則需要使用 setPath 方法重新關聯。
有參建構函式:
1 | PathMeasure(Path path,booleanforceClosed) |
用這個建構函式是建立一個 PathMeasure 並關聯一個 Path, 其實和建立一個空的 PathMeasure 後呼叫 setPath 進行關聯效果是一樣的,同樣,被關聯的 Path 也必須是已經建立好的,如果關聯之後 Path 內容進行了更改,則需要使用 setPath 方法重新關聯。
該方法有兩個引數,第一個引數自然就是被關聯的 Path 了,第二個引數是用來確保 Path 閉合,如果設定為 true, 則不論之前Path是否閉合,都會自動閉合該 Path(如果Path可以閉合的話)。
在這裡有兩點需要明確:
- 1. 不論 forceClosed 設定為何種狀態(true 或者 false), 都不會影響原有Path的狀態,即 Path 與 PathMeasure 關聯之後,之前的的 Path 不會有任何改變。
- 2. forceClosed 的設定狀態可能會影響測量結果,如果 Path 未閉合但在與 PathMeasure 關聯的時候設定 forceClosed 為 true 時,測量結果可能會比 Path 實際長度稍長一點,獲取到到是該 Path 閉合時的狀態。
下面我們用一個例子來驗證一下:
12345678910111213141516 | canvas.translate(mViewWidth/2,mViewHeight/2);Path path=newPath();path.lineTo(0,200);path.lineTo(200,200);path.lineTo(200,0);PathMeasure measure1=newPathMeasure(path,false);PathMeasure measure2=newPathMeasure(path,true);Log.e("TAG","forceClosed=false---->"+measure1.getLength());Log.e("TAG","forceClosed=true----->"+measure2.getLength());canvas.drawPath(path,mDeafultPaint); |
log如下:
123 | 25521-25521/com.gcssloop.canvasE/TAG:forceClosed=false---->600.025521-25521/com.gcssloop.canvasE/TAG:forceClosed=true----->800.0 |
繪製在介面上的效果如下:
我們所建立的 Path 實際上是一個邊長為 200 的正方形的三條邊,通過上面的示例就能驗證以上兩個問題。
- 1.我們將 Path 與兩個的 PathMeasure 進行關聯,並給 forceClosed 設定了不同的狀態,之後繪製再繪製出來的 Path 沒有任何變化,所以與 Path 與 PathMeasure進行關聯並不會影響 Path 狀態。
- 2.我們可以看到,設定 forceClosed 為 true 的方法比設定為 false 的方法測量出來的長度要長一點,這是由於 Path 沒有閉合的緣故,多出來的距離正是 Path 最後一個點與最開始一個點之間點距離。forceClosed 為 false 測量的是當前 Path 狀態的長度, forceClosed 為 true,則不論Path是否閉合測量的都是 Path 的閉合長度。
2.setPath、 isClosed 和 getLength
這三個方法都如字面意思一樣,非常簡單,這裡就簡單是敘述一下,不再過多講解。
setPath 是 PathMeasure 與 Path 關聯的重要方法,效果和 建構函式 中兩個引數的作用是一樣的。
isClosed 用於判斷 Path 是否閉合,但是如果你在關聯 Path 的時候設定 forceClosed 為 true 的話,這個方法的返回值則一定為true。
getLength 用於獲取 Path 的總長度,在之前的測試中已經用過了。
3.getSegment
getSegment 用於獲取Path的一個片段,方法如下:
1 | booleangetSegment(floatstartD,floatstopD,Path dst,booleanstartWithMoveTo) |
方法各個引數釋義:
引數 | 作用 | 備註 |
---|---|---|
返回值(boolean) | 判斷擷取是否成功 | true 表示擷取成功,結果存入dst中,false 擷取失敗,不會改變dst中內容 |
startD | 開始擷取位置距離 Path 起點的長度 | 取值範圍: 0 |
stopD | 結束擷取位置距離 Path 起點的長度 | 取值範圍: 0 |
dst | 擷取的 Path 將會新增到 dst 中 | 注意: 是新增,而不是替換 |
startWithMoveTo | 起始點是否使用 moveTo | 用於保證擷取的 Path 第一個點位置不變 |
- 如果 startD、stopD 的數值不在取值範圍 [0, getLength] 內,或者 startD == stopD 則返回值為 false,不會改變 dst 內容。
- 如果在安卓4.4或者之前的版本,在預設開啟硬體加速的情況下,更改 dst 的內容後可能繪製會出現問題,請關閉硬體加速或者給 dst 新增一個單個操作,例如: dst.rLineTo(0, 0)
我們先看看這個方法如何使用:
我們建立了一個 Path, 並在其中添加了一個矩形,現在我們想擷取矩形中的一部分,就是下圖中紅色的部分。
矩形邊長400dp,起始點在左上角,順時針
程式碼:
12345678910111213 | canvas.translate(mViewWidth/2,mViewHeight/2);// 平移座標系Path path=newPath();// 建立Path並添加了一個矩形path.addRect(-200,-200,200,200,Path.Direction.CW);Path dst=newPath();// 建立用於儲存擷取後內容的 PathPathMeasure measure=newPathMeasure(path,false);// 將 Path 與 PathMeasure 關聯// 擷取一部分存入dst中,並使用 moveTo 保持擷取得到的 Path 第一個點的位置不變measure.getSegment(200,600,dst,true);canvas.drawPath(dst,mDeafultPaint);// 繪製 dst |
結果如下:
從上圖可以看到我們成功到將需要到片段截取了出來,然而當 dst 中有內容時會怎樣呢?
12345678910111213 | canvas.translate(mViewWidth/2,mViewHeight/2);// 平移座標系Path path=newPath();// 建立Path並添加了一個矩形path.addRect(-200,-200,200,200,Path.Direction.CW);Path dst=newPath();// 建立用於儲存擷取後內容的 Pathdst.lineTo(-300,-300);// PathMeasure measure=newPathMeasure(path,false);// 將 Path 與 PathMeasure 關聯measure.getSegment(200,600,dst,true);// 擷取一部分 並使用 moveTo 保持擷取得到的 Path 第一個點的位置不變canvas.drawPath(dst,mDeafultPaint);// 繪製 Path |
結果如下:
從上面的示例可以看到 dst 中的線段保留了下來,可以得到結論:被擷取的 Path 片段會新增到 dst 中,而不是替換 dst 中到內容。
前面兩個例子中 startWithMoveTo 均為 true, 如果設定為false會怎樣呢?
12345678910111213 | canvas.translate(mViewWidth/2,mViewHeight/2);// 平移座標系Path path=newPath();// 建立Path並添加了一個矩形path.addRect(-200,-200,200,200,Path.Direction.CW);Path dst=newPath();// 建立用於儲存擷取後內容的 Pathdst.lineTo(-300,-300);// 在 dst 中新增一條線段PathMeasure measure=newPathMeasure(path,false);// 將 Path 與 PathMeasure 關聯measure.getSegment(200,600,dst,false);// canvas.drawPath(dst,mDeafultPaint);// 繪製 Path |
結果如下:
從該示例我們又可以得到一條結論:如果 startWithMoveTo 為 true, 則被截取出來到Path片段保持原狀,如果 startWithMoveTo 為 false,則會將截取出來的 Path 片段的起始點移動到 dst 的最後一個點,以保證 dst 的連續性。
從而我們可以用以下規則來判斷 startWithMoveTo 的取值:
取值 | 主要功用 |
---|---|
true | 保證擷取得到的 Path 片段不會發生形變 |
false | 保證儲存擷取片段的 Path(dst) 的連續性 |
4.nextContour
我們知道 Path 可以由多條曲線構成,但不論是 getLength , getgetSegment 或者是其它方法,都只會在其中第一條線段上執行,而這個 nextContour
就是用於跳轉到下一條曲線到方法,如果跳轉成功,則返回 true, 如果跳轉失敗,則返回 false。
如下,我們建立了一個 Path 並使其中包含了兩個閉合的曲線,內部的邊長是200,外面的邊長是400,現在我們使用 PathMeasure 分別測量兩條曲線的總長度。
程式碼:
12345678910111213141516171819 | canvas.translate(mViewWidth/2,mViewHeight/2);// 平移座標系Path path=newPath();path.addRect(-100,-100,100,100,Path.Direction.CW);// 新增小矩形path.addRect(-200,-200,200,200,Path.Direction.CW);// 新增大矩形canvas.drawPath(path,mDeafultPaint);// 繪製 PathPathMeasure measure=newPathMeasure(path,false);// 將Path與PathMeasure關聯floatlen1=measure.getLength();// 獲得第一條路徑的長度measure.nextContour();// 跳轉到下一條路徑floatlen2=measure.getLength();// 獲得第二條路徑的長度Log.i("LEN","len1="+len1);// 輸出兩條路徑的長度Log.i("LEN","len2="+len2); |
log輸出結果:
123 | 05-3002:00:33.89919879-19879/com.gcssloop.canvasI/LEN:len1=800.005-3002:00:33.89919879-19879/com.gcssloop.canvasI/LEN:len2=1600.0 |
通過測試,我們可以得到以下內容:
- 1.曲線的順序與 Path 中新增的順序有關。
- 2.getLength 獲取到到是當前一條曲線分長度,而不是整個 Path 的長度。
- 3.getLength 等方法是針對當前的曲線(其它方法請自行驗證)。
5.getPosTan
這個方法是用於得到路徑上某一長度的位置以及該位置的正切值:
1 | booleangetPosTan(floatdistance,float[]pos,float[]tan) |
方法各個引數釋義:
引數 | 作用 | 備註 |
---|---|---|
返回值(boolean) | 判斷獲取是否成功 | true表示成功,資料會存入 pos 和 tan 中, false 表示失敗,pos 和 tan 不會改變 |
distance | 距離 Path 起點的長度 | 取值範圍: 0 |
pos | 該點的座標值 | 座標值: (x==[0], y==[1]) |
tan | 該點的正切值 | 正切值: (x==[0], y==[1]) |
這個方法也不難理解,除了其中 tan
這個東東,這個東西是幹什麼的呢?
tan
是用來判斷 Path 的趨勢的,即在這個位置上曲線的走向,請看下圖示例,注意箭頭的方向:
可以看到 上圖中箭頭在沿著 Path 運動時,方向始終與 Path 走向保持一致,下面我們來看看程式碼是如何實現的:
首先我們需要定義幾個必要的變數:
|