1. 程式人生 > >安卓自定義View進階:Path之玩出花樣(PathMeasure)

安卓自定義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 走向保持一致,下面我們來看看程式碼是如何實現的:

首先我們需要定義幾個必要的變數: