Matrix原理
本篇的主角Matrix,是一個一直在后臺默默工作的勞動模范,雖然我們所有看到View背后都有著Matrix的功勞,但我們卻很少見到它,本篇我們就看看它是何方神圣吧。
由于Google已經對這一部分已經做了很好的封裝,所以跳過本部分對實際開發影響并不會太大,不想深究的粗略瀏覽即可,下一篇中將會詳細講解Matrix的具體用法和技巧。
Matrix簡介
Matrix是一個矩陣,主要功能是坐標映射,數值轉換。
它看起來大概是下面這樣:
Matrix作用就是坐標映射,那么為什么需要Matrix呢? 舉一個簡單的例子:
我的的手機屏幕作為物理設備,其物理坐標系是從左上角開始的,但我們在開發的時候通常不會使用這一坐標系,而是使用內容區的坐標系。
以下圖為例,我們的內容區和屏幕坐標系還相差一個通知欄加一個標題欄的距離,所以兩者是不重合的,我們在內容區的坐標系中的內容最終繪制的時候肯定要轉換為實際的物理坐標系來繪制,Matrix在此處的作用就是轉換這些數值。
假設通知欄高度為20像素,導航欄高度為40像素,那么我們在內容區的(0,0)位置繪制一個點,最終就要轉化為在實際坐標系中的(0,60)位置繪制一個點。
以上是僅作為一個簡單的示例,實際上不論2D還是3D,我們要將圖形顯示在屏幕上,都離不開Matrix,所以說Matrix是一個在背后辛勤工作的勞模。
Matrix特點
- 作用范圍更廣,Matrix在View,圖片,動畫效果等各個方面均有運用,相比與之前講解等畫布操作應用范圍更廣。
- 更加靈活,畫布操作是對Matrix的封裝,Matrix作為更接近底層的東西,必然要比畫布操作更加靈活。
- 封裝很好,Matrix本身對各個方法就做了很好的封裝,讓開發者可以很方便的操作Matrix。
- 難以深入理解,很難理解中各個數值的意義,以及操作規律,如果不了解矩陣,也很難理解前乘,后乘。
常見誤解
1.認為Matrix最下面的一行的三個參數(MPERSP_0、MPERSP_1、MPERSP_2)沒有什么太大的作用,在這里只是為了湊數。
實際上最后一行參數在3D變換中有著至關重要的作用,這一點會在后面中Camera一文中詳細介紹。
2.最后一個參數MPERSP_2被解釋為scale
的確,更改MPERSP_2的值能夠達到類似縮放的效果,但這是因為齊次坐標的緣故,并非這個參數的實際功能。
Matrix基本原理
Matrix 是一個矩陣,最根本的作用就是坐標轉換,下面我們就看看幾種常見變換的原理:
我們所用到的變換均屬于仿射變換,仿射變換是 線性變換(縮放,旋轉,錯切) 和 平移變換(平移) 的復合,由于這些概念對于我們作用并不大,此處不過多介紹,有興趣可自行了解。
基本變換有4種: 平移(translate)、縮放(scale)、旋轉(rotate) 和 錯切(skew)。
下面我們看一下四種變換都是由哪些參數控制的。
從上圖可以看到最后三個參數是控制透視的,這三個參數主要在3D效果中運用,通常為(0, 0, 1),不在本篇討論范圍內,暫不過多敘述,會在之后對文章中詳述其作用。
由于我們以下大部分的計算都是基于矩陣乘法規則,如果你已經把線性代數還給了老師,請參考一下這里: 維基百科-矩陣乘法
1.縮放(Scale)
用矩陣表示:
你可能注意到了,我們坐標多了一個1,這是使用了齊次坐標系的緣故,在數學中我們的點和向量都是這樣表示的(x, y),兩者看起來一樣,計算機無法區分,為此讓計算機也可以區分它們,增加了一個標志位,增加之后看起來是這樣:
(x, y, 1) - 點
(x, y, 0) - 向量另外,齊次坐標具有等比的性質,(2,3,1)、(4,6,2)...(2N,3N,N)表示的均是(2,3)這一個點。(將MPERSP_2解釋為scale這一誤解就源于此)。
圖例:
2.錯切(Skew)
錯切存在兩種特殊錯切,水平錯切(平行X軸)和垂直錯切(平行Y軸)。
水平錯切
用矩陣表示:
圖例:
垂直錯切
用矩陣表示:
圖例:
復合錯切
水平錯切和垂直錯切的復合。
用矩陣表示:
圖例:
3.旋轉(Rotate)
假定一個點 A(x0, y0) ,距離原點距離為 r, 與水平軸夾角為 α 度, 繞原點旋轉 θ 度, 旋轉后為點 B(x, y) 如下:
用矩陣表示:
圖例:
4.平移(Translate)
此處也是使用齊次坐標的優點體現之一,實際上前面的三個操作使用 2x2 的矩陣也能滿足需求,但是使用 2x2 的矩陣,無法將平移操作加入其中,而將坐標擴展為齊次坐標后,將矩陣擴展為 3x3 就可以將算法統一,四種算法均可以使用矩陣乘法完成。
用矩陣表示:
圖例:
Matrix復合原理
其實Matrix的多種復合操作都是使用矩陣乘法實現的,從原理上理解很簡單,但是,使用矩陣乘法也有其弱點,后面的操作可能會影響到前面到操作,所以在構造Matrix時順序很重要。
我們常用的四大變換操作,每一種操作在Matrix均有三類,前乘(pre),后乘(post)和設置(set),可以參見文末對Matrix方法表,由于矩陣乘法不滿足交換律,所以前乘(pre),后乘(post)和設置(set)的區別還是很大的。
前乘(pre)
這表示一個矩陣與一個特殊矩陣前乘后構造出結果矩陣。
后乘(post)
這表示一個矩陣與一個特殊矩陣后乘后構造出結果矩陣。
設置(set)
設置使用的不是矩陣乘法,而是直接覆蓋掉原來的數值,所以,使用設置可能會導致之前的操作失效。
組合
我們使用Matrix最終目的就是讓視圖顯示為我們想要的狀態,為此我們可能需要多種操作結合使用。
我發現很多講解Matrix的文章喜歡用繞某一個點縮放(旋轉)的示例來講解,如下:
那么我們如果想讓它基于圖片中心縮放,應該該怎么辦?要用到組合變換, 1)先將圖片由中心平移到原點,這是應用變換 T 2)對圖應用縮放變換 S 3)再將圖片平移回到中心,應用變換 -T 對應代碼: matrix.postScale(0.5f, 0.5f); matrix.preTranslate(-pivotX, -pivotY); matrix.postTranslate(pivotX, pivotY); PS: 此段文字引用自其它文章。
首先,這個思路是沒有任何問題的,也是實現繞某一點操作的核心原理,但這可能會對一部分小白造成誤解,認為只能這樣實現,然而查看一下Matrix的方法表就能知道四大操作都可以指定中心點,所以,上面的三行代碼用一行就能完成:
matrix.postScale(0.5f, 0.5f, pivotX, pivotY);
組合操作構造Matrix時,個人建議盡量全部使用后乘或者全部使用前乘,這樣操作順序容易確定,出現問題也比較容易排查。
當然,由于矩陣乘法不滿足交換律,前乘和后乘的結果是不同的,使用時應結合具體情景分析使用。
Pre與Post的區別
主要區別其實就是矩陣的乘法順序不同,pre相當于矩陣的右乘,而post相當于矩陣的左乘。
以下觀點存在歧義,故做刪除標注:
在圖像處理中,越靠近右邊的矩陣越先執行,所以pre操作會先執行,而post操作會后執行。
在實際操作中,我們每一步操作都會得出準確的計算結果,但是為什么還會用存在先后的說法? 難道真的能夠用pre和post影響計算順序? 實則不然,下面我們用一個例子說明:
Matrix matrix = new Matrix(); matrix.postScale(0.5f, 0.8f); matrix.preTranslate(1000, 1000); Log.e(TAG, "MatrixTest:3" + matrix.toShortString());在上面的操作中,如果按照正常的思路,先縮放,后平移,縮放操作執行在前,不會影響到后續的平移操作,但是執行結果卻發現平移距離變成了(500, 800)。
在上面例子中,計算順序是沒有問題的,先計算的縮放,然后計算的平移,而縮放影響到平移則是因為前一步縮放后的結果矩陣右乘了平移矩陣,這是符合矩陣乘法的運算規律的,也就是說縮放操作雖然在前卻影響到了平移操作,相當于先執行了平移操作,然后執行的縮放操作,因此才有pre操作會先執行,而post操作會后執行這一說法。
下面我們用不同對方式來構造一個矩陣:
假設我們需要先縮放再平移。
注意:
- 1.由于矩陣乘法不滿足交換律,請保證使用初始矩陣(Initial Matrix),否則可能導致運算結果不同。
- 2.注意構造順序,順序是會影響結果的。
- 3.Initial Matrix是指new出來的新矩陣,或者reset后的矩陣,是一個單位矩陣。
1.僅用pre:
Matrix m = new Matrix(); m.reset(); m.preTranslate(tx, ty); //使用pre,越靠后越先執行。 m.preScale(sx, sy);
用矩陣表示:
2.僅用post:
Matrix m = new Matrix(); m.reset(); m.postScale(sx, sy); //使用post,越靠前越先執行。 m.postTranslate(tx, ty);
用矩陣表示:
3.混合:
Matrix m = new Matrix(); m.reset(); m.preScale(sx, sy); m.postTranslate(tx, ty);
或:
Matrix m = new Matrix(); m.reset(); m.postTranslate(tx, ty); m.preScale(sx, sy);
由于此處只有兩步操作,且指定了先后,所以代碼上交換并不會影響結果。
用矩陣表示:
注意: 由于矩陣乘法不滿足交換律,請保證初始矩陣為空,如果初始矩陣不為空,則導致運算結果不同。
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 isIdentity | 求逆矩陣、 是否為仿射矩陣、 是否為單位矩陣 ... |
總結
對于Matrix重在理解,理解了其中的原理之后用起來將會更加得心應手。
來自:https://github.com/GcsSloop/AndroidNote/blob/master/CustomView/Advance/%5B09%5DMatrix_Basic.md