先放一個圖鎮樓,省的下面無聊的內容把你們都嚇跑了Σ( ̄。 ̄ノ)ノ
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,
boolean
forceClosed)
|
用這個構造函數是創建一個 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 閉合時的狀態。
下面我們用一個例子來驗證一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | canvas.translate(mViewWidth/2,mViewHeight/2); Path path = new Path(); path.lineTo(0,200); path.lineTo(200,200); path.lineTo(200,0); PathMeasure measure1 = new PathMeasure(path,false); PathMeasure measure2 = new PathMeasure(path,true); Log.e("TAG", "forceClosed=false---->"+measure1.getLength()); Log.e("TAG", "forceClosed=true----->"+measure2.getLength()); canvas.drawPath(path,mDeafultPaint); |
log如下:
1
2
3
|
25521-25521/com.gcssloop.canvas
E/TAG:
forceClosed=false---->600.0
25521-25521/com.gcssloop.canvas
E/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 | boolean getSegment (float startD, float stopD, Path dst, boolean startWithMoveTo) |
方法各個參數釋義:
參數 | 作用 | 備註 |
---|---|---|
返回值(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,起始點在左上角,順時針
代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
canvas.translate(mViewWidth
/
2,
mViewHeight
/
2); //
平移座標系
Path
path
=
new
Path();
//
創建Path並添加了一個矩形
path.addRect(-200,
-200,
200,
200,
Path.Direction.CW);
Path
dst
=
new
Path(); //
創建用於存儲截取後內容的 Path
PathMeasure
measure
=
new
PathMeasure(path,
false);
//
將 Path 與 PathMeasure 關聯
//
截取一部分存入dst中,並使用 moveTo 保持截取得到的 Path 第一個點的位置不變
measure.getSegment(200,
600,
dst,
true);
canvas.drawPath(dst,
mDeafultPaint); //
繪製 dst
|
結果如下:
從上圖可以看到我們成功到將需要到片段截取了出來,然而當 dst 中有內容時會怎樣呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 | canvas.translate(mViewWidth / 2, mViewHeight / 2); // 平移座標系 Path path = new Path(); // 創建Path並添加了一個矩形 path.addRect(-200, -200, 200, 200, Path.Direction.CW); Path dst = new Path(); // 創建用於存儲截取後內容的 Path dst.lineTo(-300, -300); // PathMeasure measure = new PathMeasure(path, false); // 將 Path 與 PathMeasure 關聯 measure.getSegment(200, 600, dst, true); // 截取一部分 並使用 moveTo 保持截取得到的 Path 第一個點的位置不變 canvas.drawPath(dst, mDeafultPaint); // 繪製 Path |
結果如下:
從上面的示例可以看到 dst 中的線段保留了下來,可以得到結論:被截取的 Path 片段會添加到 dst 中,而不是替換 dst 中到內容。
前面兩個例子中 startWithMoveTo 均爲 true, 如果設置爲false會怎樣呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
|
canvas.translate(mViewWidth
/
2,
mViewHeight
/
2); //
平移座標系
Path
path
=
new
Path();
//
創建Path並添加了一個矩形
path.addRect(-200,
-200,
200,
200,
Path.Direction.CW);
Path
dst
=
new
Path(); //
創建用於存儲截取後內容的 Path
dst.lineTo(-300,
-300);
//
在 dst 中添加一條線段
PathMeasure
measure
=
new
PathMeasure(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 分別測量兩條曲線的總長度。
代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | canvas.translate(mViewWidth / 2, mViewHeight / 2); // 平移座標系 Path path = new Path(); path.addRect(-100, -100, 100, 100, Path.Direction.CW); // 添加小矩形 path.addRect(-200, -200, 200, 200, Path.Direction.CW); // 添加大矩形 canvas.drawPath(path,mDeafultPaint); // 繪製 Path PathMeasure measure = new PathMeasure(path, false); // 將Path與PathMeasure關聯 float len1 = measure.getLength(); // 獲得第一條路徑的長度 measure.nextContour(); // 跳轉到下一條路徑 float len2 = measure.getLength(); // 獲得第二條路徑的長度 Log.i("LEN","len1="+len1); // 輸出兩條路徑的長度 Log.i("LEN","len2="+len2); |
log輸出結果:
1
2
3
|
05-30
02:00:33.899
19879-19879/com.gcssloop.canvas
I/LEN:
len1=800.0
05-30
02:00:33.899
19879-19879/com.gcssloop.canvas
I/LEN:
len2=1600.0
|
通過測試,我們可以得到以下內容:
- 1.曲線的順序與 Path 中添加的順序有關。
- 2.getLength 獲取到到是當前一條曲線分長度,而不是整個 Path 的長度。
- 3.getLength 等方法是針對當前的曲線(其它方法請自行驗證)。
5.getPosTan
這個方法是用於得到路徑上某一長度的位置以及該位置的正切值:
1 | boolean getPosTan (float distance, 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 走向保持一致,下面我們來看看代碼是如何實現的:
首先我們需要定義幾個必要的變量:
1
2
3
4
5
6
|
private
float
currentValue
=
0;
//
用於紀錄當前的位置,取值範圍[0,1]映射Path的整個長度
private
float[]
pos; //
當前點的實際位置
private
float[]
tan; //
當前點的tangent值,用於計算圖片所需旋轉的角度
private
Bitmap
mBitmap;
//
箭頭圖片
private
Matrix
mMatrix;
//
矩陣,用於對圖片進行一些操作
|
初始化這些變量(在構造函數中調用這個方法):
1 2 3 4 5 6 7 8 | private void init(Context context) { pos = new float[2]; tan = new float[2]; BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; // 縮放圖片 mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.arrow, options); mMatrix = new Matrix(); } |
具體繪製:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
canvas.translate(mViewWidth
/
2,
mViewHeight
/
2); //
平移座標系
Path
path
=
new
Path();
//
創建 Path
path.addCircle(0,
0,
200,
Path.Direction.CW);
//
添加一個圓形
PathMeasure
measure
=
new
PathMeasure(path,
false);
//
創建 PathMeasure
currentValue
+=
0.005; //
計算當前的位置在總長度上的比例[0,1]
if
(currentValue
>=
1)
{
currentValue
=
0;
}
measure.getPosTan(measure.getLength()
*
currentValue,
pos,
tan); //
獲取當前位置的座標以及趨勢
mMatrix.reset(); //
重置Matrix
float
degrees
=
(float)
(Math.atan2(tan[1],
tan[0])
*
180.0
/
Math.PI);
//
計算圖片旋轉角度
mMatrix.postRotate(degrees,
mBitmap.getWidth()
/
2,
mBitmap.getHeight()
/
2);
//
旋轉圖片
mMatrix.postTranslate(pos[0]
-
mBitmap.getWidth()
/
2,
pos[1]
-
mBitmap.getHeight()
/
2);
//
將圖片繪製中心調整到與當前點重合
canvas.drawPath(path,
mDeafultPaint);
//
繪製 Path
canvas.drawBitmap(mBitmap,
mMatrix,
mDeafultPaint);
//
繪製箭頭
invalidate();
//
重繪頁面
|
核心要點:
- 1.通過
tan
得值計算出圖片旋轉的角度,tan 是 tangent 的縮寫,即中學中常見的正切, 其中tan0是鄰邊邊長,tan1是對邊邊長,而Math中atan2
方法是根據正切是數值計算出該角度的大小,得到的單位是弧度,所以上面又將弧度轉爲了角度。- 2.通過
Matrix
來設置圖片對旋轉角度和位移,這裏使用的方法與前面講解過對 canvas操作 有些類似,對於Matrix
會在後面專一進行講解,敬請期待。- 3.頁面刷新,頁面刷新此處是在 onDraw 裏面調用了 invalidate 方法來保持界面不斷刷新,但並不提倡這麼做,正確對做法應該是使用 線程 或者 ValueAnimator 來控制界面的刷新,關於控制頁面刷新這一部分會在後續的 動畫部分 詳細講解,同樣敬請期待。
6.getMatrix
這個方法是用於得到路徑上某一長度的位置以及該位置的正切值的矩陣:
1 | boolean getMatrix (float distance, Matrix matrix, int flags) |
方法各個參數釋義:
參數 | 作用 | 備註 |
---|---|---|
返回值(boolean) | 判斷獲取是否成功 | true表示成功,數據會存入matrix中,false 失敗,matrix內容不會改變 |
distance | 距離 Path 起點的長度 | 取值範圍: 0 |
matrix | 根據 falgs 封裝好的matrix | 會根據 flags 的設置而存入不同的內容 |
flags | 規定哪些內容會存入到matrix中 | 可選擇 POSITION_MATRIX_FLAG(位置) ANGENT_MATRIX_FLAG(正切) |
其實這個方法就相當於我們在前一個例子中封裝 matrix
的過程由 getMatrix
替我們做了,我們可以直接得到一個封裝好到 matrix
,豈不快哉。
但是我們看到最後到 flags
選項可以選擇 位置
或者 正切
,如果我們兩個選項都想選擇怎麼辦?
如果兩個選項都想選擇,可以將兩個選項之間用 |
連接起來,如下:
1
2
|
measure.getMatrix(distance,
matrix,
PathMeasure.TANGENT_MATRIX_FLAG
|
PathMeasure.POSITION_MATRIX_FLAG);
|
我們可以將上面都例子中 getPosTan
替換爲 getMatrix
,
看看是不是會顯得簡單很多:
具體繪製:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
Path
path
=
new
Path();
//
創建 Path
path.addCircle(0,
0,
200,
Path.Direction.CW);
//
添加一個圓形
PathMeasure
measure
=
new
PathMeasure(path,
false);
//
創建 PathMeasure
currentValue
+=
0.005; //
計算當前的位置在總長度上的比例[0,1]
if
(currentValue
>=
1)
{
currentValue
=
0;
}
//
獲取當前位置的座標以及趨勢的矩陣
measure.getMatrix(measure.getLength()
*
currentValue,
mMatrix,
PathMeasure.TANGENT_MATRIX_FLAG
|
PathMeasure.POSITION_MATRIX_FLAG);
mMatrix.preTranslate(-mBitmap.getWidth()
/
2,
-mBitmap.getHeight()
/
2);
//
canvas.drawPath(path,
mDeafultPaint);
//
繪製 Path
canvas.drawBitmap(mBitmap,
mMatrix,
mDeafultPaint);
//
繪製箭頭
invalidate();
//
重繪頁面
|
由於此處代碼運行結果與上面一樣,便不再貼圖片了,請參照上面一個示例的效果圖。
可以看到使用 getMatrix 方法的確可以節省一些代碼,不過這裏依舊需要注意一些內容:
- 1.對
matrix
的操作必須要在getMatrix
之後進行,否則會被getMatrix
重置而導致無效。- 2.矩陣對旋轉角度默認爲圖片的左上角,我們此處需要使用
preTranslate
調整爲圖片中心。- 3.pre(矩陣前乘) 與 post(矩陣後乘) 的區別,此處請等待後續的文章或者自行搜索。
Path & SVG
我們知道,用Path可以創建出各種個樣的圖形,但如果圖形過於複雜時,用代碼寫就不現實了,不僅麻煩,而且容易出錯,所以在繪製複雜的圖形時我們一般是將 SVG 圖像轉換爲 Path。
你說什麼是 SVG?
SVG 是一種矢量圖,內部用的是 xml 格式化存儲方式存儲這操作和數據,你完全可以將 SVG 看作是 Path 的各項操作簡化書寫後的存儲格式。
Path 和 SVG 結合通常能誕生出一些奇妙的東西,如下:
該圖片來自這個開源庫 ->PathView
SVG 轉 Path 的解析可以用這個庫 -> AndroidSVG
限於篇幅以及本人精力,這一部分就暫不詳解了,感興趣的可以直接看源碼,或者搜索一些相關的解析文章。
Path使用技巧
話說本篇文章的名字不是叫 玩出花樣麼?怎麼只見前面囉囉嗦嗦的扯了一大堆不明所以的東西,花樣在哪裏?
前面的內容雖然囉嗦繁雜,但卻是重中之重的基礎,如果在修仙界,這叫根基,而下面講述的內容的是招式,有了根基才能演化出千變萬化的招式,而沒有根基只學招式則是徒有其表,只能學一樣會一樣,很難適應千變萬化的需求。
先放一個效果圖,然後分析一下實現過程:
這是一個搜索的動效圖,通過分析可以得到它應該有四種狀態,分別如下:
狀態 | 概述 |
---|---|
初始狀態 |
初始狀態,沒有任何動效,只顯示一個搜索標誌
想對作者說點什麼?
我來說一句
沒有更多推薦了,返回首頁 |