安卓自定義View進階-Matrix詳解
這應該是目前最詳細的一篇講解Matrix的中文文章了,在上一篇文章Matrix原理中,我們對Matrix做了一個簡單的瞭解,偏向理論,在本文中則會詳細的講解Matrix的具體用法,以及與Matrix相關的一些實用技巧。
⚠️ 警告:測試本文章示例之前請關閉硬體加速。
Matrix方法表
按照慣例,先放方法表做概覽。
方法類別 | 相關API | 摘要 |
---|---|---|
基本方法 | equals hashCode toString toShortString | 比較、 獲取雜湊值、 轉換為字串 |
數值操作 | set reset setValues getValues | 設定、 重置、 設定數值、 獲取數值 |
數值計算 | mapPoints mapRadius mapRect mapVectors | 計算變換後的數值 |
設定(set) | setConcat setRotate setScale setSkew setTranslate | 設定變換 |
前乘(pre) | preConcat preRotate preScale preSkew preTranslate | 前乘變換 |
後乘(post) | postConcat postRotate postScale postSkew postTranslate | 後乘變換 |
特殊方法 | setPolyToPoly setRectToRect rectStaysRect setSinCos | 一些特殊操作 |
矩陣相關 | invert isAffine(API21) isIdentity | 求逆矩陣、 是否為仿射矩陣、 是否為單位矩陣 … |
Matrix方法詳解
構造方法
構造方法沒有在上面表格中列出。
無參構造
Matrix ()
建立一個全新的Matrix,使用格式如下:
Matrix matrix = new Matrix();
通過這種方式創建出來的並不是一個數值全部為空的矩陣,而是一個單位矩陣,如下:
有參構造
Matrix (Matrix src)
這種方法則需要一個已經存在的矩陣作為引數,使用格式如下:
Matrix matrix = new Matrix(src);
建立一個Matrix,並對src深拷貝(理解為新的matrix和src是兩個物件,但內部數值相同即可)。
基本方法
基本方法內容比較簡單,在此處簡要介紹一下。
1.equals
比較兩個Matrix的數值是否相同。
2.hashCode
獲取Matrix的雜湊值。
3.toString
將Matrix轉換為字串: Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]}
4.toShortString
將Matrix轉換為短字串: [1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
數值操作
數值操作這一組方法可以幫助我們直接控制Matrix裡面的數值。
1.set
void set (Matrix src)
沒有返回值,有一個引數,作用是將引數Matrix的數值複製到當前Matrix中。如果引數為空,則重置當前Matrix,相當於reset()
。
2.reset
void reset ()
重置當前Matrix(將當前Matrix重置為單位矩陣)。
3.setValues
void setValues (float[] values)
setValues的引數是浮點型的一維陣列,長度需要大於9,拷貝陣列中的前9位數值賦值給當前Matrix。
4.getValues
void getValues (float[] values)
很顯然,getValues和setValues是一對方法,引數也是浮點型的一維陣列,長度需要大於9,將Matrix中的數值拷貝進引數的前9位中。
數值計算
1.mapPoints
void mapPoints (float[] pts)
void mapPoints (float[] dst, float[] src)
void mapPoints (float[] dst, int dstIndex,float[] src, int srcIndex, int pointCount)
計算一組點基於當前Matrix變換後的位置,(由於是計算點,所以引數中的float陣列長度一般都是偶數的,若為奇數,則最後一個數值不參與計算)。
它有三個過載方法:
(1) void mapPoints (float[] pts)
方法僅有一個引數,pts陣列作為引數傳遞原始數值,計算結果仍存放在pts中。
示例:
// 初始資料為三個點 (0, 0) (80, 100) (400, 300)
float[] pts = new float[]{0, 0, 80, 100, 400, 300};
// 構造一個matrix,x座標縮放0.5
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f);
// 輸出pts計算之前資料
Log.i(TAG, "before: "+ Arrays.toString(pts));
// 呼叫map方法計算
matrix.mapPoints(pts);
// 輸出pts計算之後資料
Log.i(TAG, "after : "+ Arrays.toString(pts));
結果:
before: [0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
after : [0.0, 0.0, 40.0, 100.0, 200.0, 300.0]
(2) void mapPoints (float[] dst, float[] src)
,src作為引數傳遞原始數值,計算結果存放在dst中,src不變。
如果原始資料需要保留則一般使用這種方法。
示例:
// 初始資料為三個點 (0, 0) (80, 100) (400, 300)
float[] src = new float[]{0, 0, 80, 100, 400, 300};
float[] dst = new float[6];
// 構造一個matrix,x座標縮放0.5
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f);
// 輸出計算之前資料
Log.i(TAG, "before: src="+ Arrays.toString(src));
Log.i(TAG, "before: dst="+ Arrays.toString(dst));
// 呼叫map方法計算
matrix.mapPoints(dst,src);
// 輸出計算之後資料
Log.i(TAG, "after : src="+ Arrays.toString(src));
Log.i(TAG, "after : dst="+ Arrays.toString(dst));
結果:
before: src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
before: dst=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
after : src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
after : dst=[0.0, 0.0, 40.0, 100.0, 200.0, 300.0]
(3) void mapPoints (float[] dst, int dstIndex,float[] src, int srcIndex, int pointCount)
可以指定只計算一部分數值。
引數 | 摘要 |
---|---|
dst | 目標資料 |
dstIndex | 目標資料儲存位置起始下標 |
src | 源資料 |
srcIndex | 源資料儲存位置起始下標 |
pointCount | 計算的點個數 |
示例:
將第二、三個點計算後儲存進dst最開始位置。
// 初始資料為三個點 (0, 0) (80, 100) (400, 300)
float[] src = new float[]{0, 0, 80, 100, 400, 300};
float[] dst = new float[6];
// 構造一個matrix,x座標縮放0.5
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f);
// 輸出計算之前資料
Log.i(TAG, "before: src="+ Arrays.toString(src));
Log.i(TAG, "before: dst="+ Arrays.toString(dst));
// 呼叫map方法計算(最後一個2表示兩個點,即四個數值,並非兩個數值)
matrix.mapPoints(dst, 0, src, 2, 2);
// 輸出計算之後資料
Log.i(TAG, "after : src="+ Arrays.toString(src));
Log.i(TAG, "after : dst="+ Arrays.toString(dst));
結果:
before: src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
before: dst=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
after : src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
after : dst=[40.0, 100.0, 200.0, 300.0, 0.0, 0.0]
2.mapRadius
float mapRadius (float radius)
測量半徑,由於圓可能會因為畫布變換變成橢圓,所以此處測量的是平均半徑。
示例:
float radius = 100;
float result = 0;
// 構造一個matrix,x座標縮放0.5
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f);
Log.i(TAG, "mapRadius: "+radius);
result = matrix.mapRadius(radius);
Log.i(TAG, "mapRadius: "+result);
結果:
mapRadius: 100.0
mapRadius: 70.71068
3.mapRect
boolean mapRect (RectF rect)
boolean mapRect (RectF dst, RectF src)
測量矩形變換後位置。
(1) boolean mapRect (RectF rect)
測量rect並將測量結果放入rect中,返回值是判斷矩形經過變換後是否仍為矩形。
示例:
RectF rect = new RectF(400, 400, 1000, 800);
// 構造一個matrix
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f);
matrix.postSkew(1,0);
Log.i(TAG, "mapRadius: "+rect.toString());
boolean result = matrix.mapRect(rect);
Log.i(TAG, "mapRadius: "+rect.toString());
Log.e(TAG, "isRect: "+ result);
結果:
mapRadius: RectF(400.0, 400.0, 1000.0, 800.0)
mapRadius: RectF(600.0, 400.0, 1300.0, 800.0)
isRect: false
由於使用了錯切,所以返回結果為false。
(2) boolean mapRect (RectF dst, RectF src)
測量src並將測量結果放入dst中,返回值是判斷矩形經過變換後是否仍為矩形,和之前沒有什麼太大區別,此處就不囉嗦了。
4.mapVectors
測量向量。
void mapVectors (float[] vecs)
void mapVectors (float[] dst, float[] src)
void mapVectors (float[] dst, int dstIndex, float[] src, int srcIndex, int vectorCount)
mapVectors
與 mapPoints
基本上是相同的,可以直接參照上面的mapPoints
使用方法。
而兩者唯一的區別就是mapVectors
不會受到位移的影響,這符合向量的定律,如果你不瞭解的話,請找到以前教過你的老師然後把學費要回來。
區別:
float[] src = new float[]{1000, 800};
float[] dst = new float[2];
// 構造一個matrix
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f);
matrix.postTranslate(100,100);
// 計算向量, 不受位移影響
matrix.mapVectors(dst, src);
Log.i(TAG, "mapVectors: "+Arrays.toString(dst));
// 計算點
matrix.mapPoints(dst, src);
Log.i(TAG, "mapPoints: "+Arrays.toString(dst));
結果:
mapVectors: [500.0, 800.0]
mapPoints: [600.0, 900.0]
set、pre 與 post
對於四種基本變換 平移(translate)、縮放(scale)、旋轉(rotate)、 錯切(skew) 它們每一種都三種操作方法,分別為 設定(set)、 前乘(pre) 和 後乘 (post)。而它們的基礎是Concat,通過先構造出特殊矩陣然後用原始矩陣Concat特殊矩陣,達到變換的結果。
關於四種基本變換的知識和三種對應操作的區別,詳細可以參考 Canvas之畫布操作 和 Matrix原理 這兩篇文章的內容。
由於之前的文章已經詳細的講解過了它們的原理與用法,所以此處就簡要的介紹一下:
方法 | 簡介 |
---|---|
set | 設定,會覆蓋掉之前的數值,導致之前的操作失效。 |
pre | 前乘,相當於矩陣的右乘, M' = M * S (S指為特殊矩陣) |
post | 後乘,相當於矩陣的左乘,M' = S * M (S指為特殊矩陣) |
Matrix 相關的重要知識:
-
1.一開始從Canvas中獲取到到Matrix並不是初始矩陣,而是經過偏移後到矩陣,且偏移距離就是距離螢幕左上角的位置。
-
這個可以用於判定View在螢幕上的絕對位置,View可以根據所處位置做出調整。
-
2.構造Matrix時使用的是矩陣乘法,前乘(pre)與後乘(post)結果差別很大。
-
這個直接參見上一篇文章 Matrix原理 即可。
-
3.受矩陣乘法影響,後面的執行的操作可能會影響到之前的操作。
-
使用時需要注意構造順序。
特殊方法
這一類方法看似不起眼,但拿來稍微加工一下就可能製作意想不到的效果。
1.setPolyToPoly
boolean setPolyToPoly (
float[] src, // 原始陣列 src [x,y],儲存內容為一組點
int srcIndex, // 原始陣列開始位置
float[] dst, // 目標陣列 dst [x,y],儲存內容為一組點
int dstIndex, // 目標陣列開始位置
int pointCount) // 測控點的數量 取值範圍是: 0到4
Poly全稱是Polygon,多邊形的意思,瞭解了意思大致就能知道這個方法是做什麼用的了,應該與PS中自由變換中的扭曲有點類似。
從引數我們可以瞭解到setPolyToPoly最多可以支援4個點,這四個點通常為圖形的四個角,可以通過這四個角將檢視從矩形變換成其他形狀。
簡單示例:
public class MatrixSetPolyToPolyTest extends View {
private Bitmap mBitmap; // 要繪製的圖片
private Matrix mPolyMatrix; // 測試setPolyToPoly用的Matrix
public MatrixSetPolyToPolyTest(Context context) {
super(context);
initBitmapAndMatrix();
}
private void initBitmapAndMatrix() {
mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.poly_test);
mPolyMatrix = new Matrix();
float[] src = {0, 0, // 左上
mBitmap.getWidth(), 0, // 右上
mBitmap.getWidth(), mBitmap.getHeight(), // 右下
0, mBitmap.getHeight()}; // 左下
float[] dst = {0, 0, // 左上
mBitmap.getWidth(), 400, // 右上
mBitmap.getWidth(), mBitmap.getHeight() - 200, // 右下
0, mBitmap.getHeight()}; // 左下
// 核心要點
mPolyMatrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1); // src.length >> 1 為位移運算 相當於處以2
// 此處為了更好的顯示對圖片進行了等比縮放和平移(圖片本身有點大)
mPolyMatrix.postScale(0.26f, 0.26f);
mPolyMatrix.postTranslate(0,200);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 根據Matrix繪製一個變換後的圖片
canvas.drawBitmap(mBitmap, mPolyMatrix, null);
}
}
文章發出後有小夥伴在GitHub上提出疑問,說此處講解到並不清楚,尤其是最後的一個引數,所以特此補充一下內容。
我們知道pointCount
支援點的個數為0到4個,四個一般指圖形的四個角,屬於最常用的一種情形,但前面幾種是什麼情況呢?
釋出此文的時候之所以沒有講解0到3的情況,是因為前面的幾種情況在實際開發中很少會出現,
才不是因為偷懶呢,哼。
pointCount | 摘要 |
---|---|
0 | 相當於reset |
1 | 相當於translate |
2 | 可以進行 縮放、旋轉、平移 變換 |
3 | 可以進行 縮放、旋轉、平移、錯切 變換 |
4 | 可以進行 縮放、旋轉、平移、錯切以及任何形變 |
從上表我們可以觀察出一個規律, 隨著
pointCount
數值增大setPolyToPoly的可以操作性也越來越強,這不是廢話麼,可調整點數多了能幹的事情自然也多了。只列一個表格就算交代完畢了顯得誠意不足,為了彰顯誠意,接下來詳細的講解一下。
為什麼說前面幾種情況在實際開發中很少出現?
作為開發人員,寫出來的程式碼出了要讓機器”看懂”,沒有歧義之外,最重要的還是讓人看懂,以方便後期的維護修改,從上邊的表格中可以看出,前面的幾種種情況都可以有更直觀的替代方法,只有四個引數的情況下的特殊形變是沒有替代方法的。
測控點選取位置?
測控點可以選擇任何你認為方便的位置,只要src與dst一一對應即可。不過為了方便,通常會選擇一些特殊的點: 圖形的四個角,邊線的中心點以及圖形的中心點等。不過有一點需要注意,測控點選取都應當是不重複的(src與dst均是如此),如果選取了重複的點會直接導致測量失效,這也意味著,你不允許將一個方形(四個點)對映為三角形(四個點,但其中兩個位置重疊),但可以接近於三角形。。
作用範圍?
作用範圍當然是設定了Matrix的全部區域,如果你將這個Matrix賦值給了Canvas,它的作用範圍就是整個畫布,如果你賦值給了Bitmap,它的作用範圍就是整張圖片。
接下來用示例演示一下,所有示例的src均為圖片大小,dst根據手勢變化。
pointCount為0
pointCount為0和reset
是等價的,而不是保持matrix不變,在最底層的實現中可以看到這樣的程式碼:
if (0 == count) {
this->reset();
return true;
}
pointCount為1
pointCount為0和translate
是等價的,在最底層的實現中可以看到這樣的程式碼:
if (1 == count) {
this->setTranslate(dst[0].fX - src[0].fX, dst[0].fY - src[0].fY);
return true;
}
平移的距離是dst - src.
當測控點為1的時候,由於你只有一個點可以控制,所以你只能拖拽著它在2D平面上滑動。
pointCount為2
當pointCount為2的時候,可以做縮放、平移和旋轉。
pointCount為3
當pointCount為3的時候,可以做縮放、平移、旋轉和錯切。
pointCount為4
當pointCount為4的時候,你可以將影象拉伸為任意四邊形。
相關推薦
安卓自定義View進階-Matrix詳解
這應該是目前最詳細的一篇講解Matrix的中文文章了,在上一篇文章Matrix原理中,我們對Matrix做了一個簡單的瞭解,偏向理論,在本文中則會詳細的講解Matrix的具體用法,以及與Matrix相關的一些實用技巧。 ⚠️ 警告:測試本文章示例之前請關閉硬體加速。
安卓自定義View進階-MotionEvent詳解
Android MotionEvent 詳解,之前用了兩篇文章 事件分發機制原理 和 事件分發機制詳解 來講解事件分發,而作為事件分發主角之一的 MotionEvent 並沒有過多的說明,本文就帶大家瞭解 MotionEvent 的相關內容,簡要介紹觸控事件,主要包括 單點觸控、多點
安卓自定義View進階-Matrix Camera
本篇依舊屬於Matrix,主要講解Camera,Android下有很多相機應用,其中的美顏相機更是不少,不過今天這個Camera可不是我們平時拍照的那個相機,而是graphic包下的Camera,專業給View拍照的相機,不過既然是相機,作用都是類似的,主要是將3D的內容拍扁變成2D
安卓自定義View進階-Matrix原理
本文內容偏向理論,和 畫布操作 有重疊的部分,本文會讓你更加深入的瞭解其中的原理。 本篇的主角Matrix,是一個一直在後臺默默工作的勞動模範,雖然我們所有看到View背後都有著Matrix的功勞,但我們卻很少見到它,本篇我們就看看它是何方神聖吧。 由於Goog
安卓自定義View進階-手勢檢測(GestureDecetor)
Android 手勢檢測,主要是 GestureDetector 相關內容的用法和注意事項,本文依舊屬於事件處理這一體系,部分內容會涉及到之前文章提及過的知識點,如果你沒看過之前的文章,可以到 自定義 View 系列 來檢視這些內容。 在開發 Android 手機應用過程中,可
安卓自定義View進階-多點觸控詳解
Android 多點觸控詳解,在前面的幾篇文章中我們大致瞭解了 Android 中的事件處理流程和一些簡單的處理方案,本次帶大家瞭解 Android 多點觸控相關的一些知識。 多點觸控 ( Multitouch,也稱 Multi-touch ),即同時接受螢幕上多個點的人機互動
安卓自定義View進階-特殊控制元件的事件處理方案
本文帶大家瞭解 Android 特殊形狀控制元件的事件處理方式,主要是利用了 Region 和 Matrix 的一些方法,超級實用的事件處理方案,相信看完本篇之後,任何奇葩控制元件的事件處理都會變得十分簡單。 不得不說,Android 對事件體系封裝的非常棒,即便對事件體系不太
安卓自定義View進階-事件分發機制詳解
Android 事件分發機制詳解,在上一篇文章 事件分發機制原理 中簡要分析了一下事件分發機制的原理,原理是十分簡單的,一句話就能總結:責任鏈模式,事件層層傳遞,直到被消費。 雖然原理簡單,但是隨著 Android 不斷的發展,實際運用場景也越來越複雜,所以想要徹底玩轉事件分發機制還
安卓自定義View進階-PathMeasure
可以看到,在經過 Path之基本操作 Path之貝塞爾曲線 和 Path之完結篇 後, Path中各類方法基本上都講完了,表格中還沒有講解到到方法就是矩陣變換了,難道本篇終於要講矩陣了? 非也,矩陣這一部分仍在後面單獨講解,本篇主要講解 PathMeasure 這個類與 Path 的
安卓自定義View進階-Path之貝塞爾曲線
在上一篇文章Path之基本操作中我們瞭解了Path的基本使用方法,本次瞭解Path中非常非常非常重要的內容-貝塞爾曲線。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除了在API21(即安卓版本5.0)以上
安卓自定義View進階-Path之基本操作
在上一篇Canvas之圖片文字中我們瞭解瞭如何使用Canvas中繪製圖片文字,結合前幾篇文章,Canvas的基本操作已經差不多完結了,然而Canvas不僅僅具有這些基本的操作,還可以更加炫酷,本次會了解到path(路徑)這個Canvas中的神器,有了這個神器,就能創造出更多炫(zhu
安卓自定義View進階-Canvas之圖片文字
在上一篇文章Canvas之畫布操作中我們瞭解了畫布的一些基本操作方法,本次瞭解一些繪製圖片文字相關的內容。如果你對前幾篇文章講述的內容熟練掌握的話,那麼恭喜你,本篇結束之後,大部分的自定義View已經難不倒你了,當然了,這並不是終點,接下來還會有更加炫酷的技能。 一.Canva
安卓自定義View進階-分類與流程
本章節為什麼要叫進階篇?(雖然講的是基礎內容),因為從本篇開始,將會逐漸揭開自定義View的神祕面紗,每一篇都將比上一篇內容更加深入,利用所學的知識能夠製作更加炫酷自定義View,就像在臺階上一樣,每一篇都更上一層,幫助大家一步步走向人生巔峰,出任CEO,迎娶白富美。 誤
安卓自定義View進階-Path之完結篇
經歷過前兩篇 Path之基本操作 和 Path之貝塞爾曲線 的講解,本篇終於進入Path的收尾篇,本篇結束後Path的大部分相關方法都已經講解完了,但Path還有一些更有意思的玩法,應該會在後續的文章中出現。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除
安卓自定義View進階-縮放手勢檢測(ScaleGestureDecetor)
0. 前言 Android 縮放手勢檢測,ScaleGestureDetector 相關內容的用法和注意事項,本文依舊屬於事件處理這一體系,在大多數的情況下,縮放手勢都不是單獨存在的,需要配合其它的手勢來使用,所以推薦配合 手勢檢測(GestureDetector) 一
安卓自定義View進階-Canvas之畫布操作
Canvas之畫布操作 上一篇Canvas之繪製基本形狀中我們瞭解瞭如何使用Canvas繪製基本圖形,本次瞭解一些基本的畫布操作。 本來想把畫布操作放到後面部分的,但是發現很多圖形繪製都離不開畫布操作,於是先講解一下畫布的基本操作方法。
安卓自定義 View 進階:貝塞爾曲線
在上一篇文章Path之基本圖形中我們瞭解了Path的基本使用方法,本次瞭解Path中非常非常非常重要的內容-貝塞爾曲線。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除了在API21(即安卓版本5.0)以上才新增的方法。忍不住吐槽一下,為啥看起來有些順手就能寫的
安卓自定義View進階:Path基本操作
在上一篇Canvas之圖片文字中我們瞭解瞭如何使用Canvas中繪製圖片文字,結合前幾篇文章,Canvas的基本操作已經差不多完結了,然而Canvas不僅僅具有這些基本的操作,還可以更加炫酷,本次會了解到path(路徑)這個Canvas中的神器,有了這個神器,就能創造出更多炫(
安卓自定義View進階: 圖片文字
在上一篇文章Canvas之畫布操作中我們瞭解了畫布的一些基本操作方法,本次瞭解一些繪製圖片文字相關的內容。如果你對前幾篇文章講述的內容熟練掌握的話,那麼恭喜你,本篇結束之後,大部分的自定義View已經難不倒你了,當然了,這並不是終點,接下來還會有更加炫酷的技能。 一.Canva
安卓自定義 View 進階:Path 完結篇(偽)
經歷過前兩篇 Path之基本操作 和 Path之貝塞爾曲線 的講解,本篇終於進入Path的收尾篇,本篇結束後Path的大部分相關方法都已經講解完了,但Path還有一些更有意思的玩法,應該會在後續的文章中出現吧,嗯,應該會的ˊ_>ˋ 一.Path常用方法表 為了相容性