深入理解 Android 中的 Matrix
在 Android 開發中,矩陣是一個功能強大並且應用廣泛的神器,例如:用它來製作動畫效果、改變圖片大小、給圖片加各類濾鏡等。對於矩陣,Android 官方 SDK 為我們提供了一個強大的類 Matrix (還有 ColorMatrix )是一直困擾著我的問題,雖然大致能夠呼叫相應的 API ,但卻一直 get 不到其內在的梗。但是出來混總是別想著矇混過關的,所以最近重新操起一年畢業的線性代數,再本著小事問老婆,大事問Google的心態,終於把多年不解的問題給破了。出於好記性不如爛筆頭的原因,便有了本文。
讀完本文,相信你能夠搞明白以下三個問題:
- 為什麼 Matrix 是個 3 X 3 的矩陣
- Matrix 這個 3 X 3 的矩陣每個元素的作用
- Matrix 的 setXXX、preXXX、postXXX API 方法的工作原理
Matrix 的結構
Matrix 是 Android SDK 提供的一個矩陣類,它代表一個 3 X 3 的矩陣(不懂矩陣為何物的童鞋就要自行 Google 了)。 Matrix 提供了讓我們獲得 Matrix 值的 API —— getValues
利用此 API 傳入一個長度為 9 的 float 陣列,即可獲得矩陣中每個元素的值。那麼這 9 個浮點數的作用和意義是什麼呢,從 Android 官方文件上看,它為這個陣列中的每一個元素都定義了一個下標常量
這個 9 個常量取值分別是 0 – 8
如果我們將這個 float 排成直觀的矩陣格式,那它將是下面這樣子的
實際上我們平常利用 Matrix 來進行 Translate(平移)、Scale(縮放)、Rotate(旋轉)的操作,就是在操作著這個矩陣中元素的數值來達到我們想要的效果。但是現在問題來了,上面提到的平移、縮放、旋轉操作中,旋轉和縮放可以用乘法表示,而平移就只能用加法表示,而且 Matrix 是一個 3 X 3 的矩陣,實際上表示這些操作 2 X 2 的矩陣足矣!
如上,可以依次看到平移、縮放、旋轉的矩陣,其中
- (x’,y’)表示執行操作後的點的座標,(x,y)表示執行操作前的點的座標
- tx、ty 分別表示x軸、y軸上平移的距離,Sx、Sy 分別表示x軸、y軸上的縮放比例
- θ 則表示旋轉角度
至於上面矩陣的推導過程,網路上很多,這裡就不去贅述了。以前到了這裡,我就會很納悶,為什麼 2 X 2 矩陣能幹的事情,偏偏要用 3 X 3 矩陣去做,直到遇到前面提到的兩篇文章才有所領悟。
其實在計算機圖形應用涉及到幾何變換,主要包括平移、旋轉、縮放。以矩陣表示式來計算這些變換時,平移是矩陣相加,旋轉和縮放則是矩陣相乘。那些數學大神們為了方便計算,所以引入了一樣神器叫做齊次座標(不懂的童鞋,老規矩自行搜尋),將平移的加法合併用乘法表示。所以,2 X 2 的矩陣經過一番變換後,成了下面這樣的。
至此,我們可以得知為什麼 Matrix 是一個 3 X 3 的矩陣,其實 2 X 2 的矩陣是足以表示的,不過是為了方便計算而合併寫成了 3 X 3 的格式。
Matrix 元素的作用
一個 Matrix 共有 9 個元素,那麼它每個元素的值發生改變會起到什麼作用呢?按照前面所示的齊次座標轉換得到 3 X 3 的矩陣和 Android 文件提供的官方結構相對應,我們不難看出下面的對應關係(其實從 Matrix 中每個位置的常量命名也可以看出來):
從這我們可以看出這個 Matrix 結構中的每個引數發揮著如下作用:
- MTRANS_X、MTRANS_Y 同時控制著 Translate
- MSCALE_X、MSCALE_Y 同時控制著 Scale
- MSCALE_X、MSKEW_X、MSCALE_Y、MSKEW_Y 同時控制著 Rotate
- 從名稱上看,我們可以順帶看出 MSKEW_X、MSKEW_Y 同時控制著 Skew
如果要進行程式碼驗證的話,也非常簡單,例如直接只對 Matrix 做 Translate 的 API 呼叫操作,再將 Matrix 的資訊列印到控制檯,你會發現整個 Matrix 確實只有 MTRANS_X、MTRANS_Y 兩個位置的數字在發生變化。其他 Scale、Rotate、Skew 操作也是一樣,感興趣的童鞋可以自行程式碼驗證一番。
至此,我們可以大致弄清矩陣每個元素的作用。至於 MPERSP_0、MPERSP_1、MPERSP_2 這三個引數,目前暫時不得而知,網上有文章說這三個引數控制著透視變換,但是文件和 API 上都沒怎麼提及,所以還是有待驗證研究的,有明白的童鞋不妨留言賜教一下,不勝感激。
理解 Matrix API 呼叫
按照第一小節裡面通過齊次座標轉換而來的矩陣方程可以知道,假設一根線執行了平移操作,相當於線上每個點的座標被下方的矩陣左乘。(縮放和旋轉操作也是同理)
如果要進行同時縮放、平移之類的符合變化操作,也無非就是選取相應的矩陣做左乘操作。為了加深矩陣變換對應 Matrix API 呼叫的理解,直接通過下面的一個自定義的動畫效果和程式碼來講解好了。
public class SimpleCustomAnimation extends Animation { private int mWidth, mHeight; @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); this.mWidth = width; this.mHeight = height; } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { Matrix matrix = t.getMatrix(); matrix.preScale(interpolatedTime, interpolatedTime);//縮放 matrix.preRotate(interpolatedTime * 360);//旋轉 //下面的Translate組合是為了將縮放和旋轉的基點移動到整個View的中心,不然系統預設是以View的左上角作為基點 matrix.preTranslate(-mWidth / 2, -mHeight / 2); matrix.postTranslate(mWidth / 2, mHeight / 2); } }
熟悉動畫這塊的童鞋肯定知道,Animation 就是通過不斷改變 applyTransformation 函式傳入的 Matrix 來實現各種各樣的動畫效果的,通過上面 applyTransformation 寥寥的幾行 Matrix 的複合變換操作可以得到如下效果
實際上這幾行程式碼用矩陣來表示就相當於如下所示:
關於程式碼的作用上邊已經給出了註釋,這裡就不多寫了。主要還是要弄明白 Matrix 複合變換中 pre 、 post 等操作與其對應的矩陣發生的左乘、右乘變化。
總結
到此,整篇文章已經完結,相信已經能夠讓你明白開頭提到的三個問題。其實我們也可以發現,Google 封裝了 Matrix 已經是很完美了,幾乎遮蔽了所有的數學細節,使得我這種數學水平一般的開發者也能夠去呼叫相應的 API 實現一些簡單的效果。雖然被封裝得很完美,但掌握相應的一些原理,依舊可以幫你更好的理解一些技術實現,此次加深了對 Matrix 一些操作的理解,幫我自己解決了以前不少的困惑,不知道有沒有幫你 get 到一些什麼呢?
上面給的示例程式碼很簡單,複製黏貼即可執行玩耍,實在需要直接執行原始碼的童鞋就到 https://github.com/D-clock/AndroidStudyCode 找吧!
相關文章
- 深入理解Android中的SharedPreferencesAndroid
- 深入理解Android中的ClassLoaderAndroid
- Android 深入理解Android中的自定義屬性Android
- 深入理解 Android 中的各種 ContextAndroidContext
- 深入理解AndroidAndroid
- 帶你深入理解Android中的自定義屬性!!!Android
- Android中深入理解 LayoutInflater.inflate()Android
- JS中this的深入理解JS
- 深入理解Js中的thisJS
- 深入理解Java中的鎖Java
- 深入理解 Java 中的 LambdaJava
- 深入理解Java中的AQSJavaAQS
- 深入理解 JavaScript 中的 classJavaScript
- 深入理解Oracle中的DBCAOracle
- 深入理解Oracle中的MutexOracleMutex
- 深入理解Oracle中的latchOracle
- 深入理解Android中的快取機制(三)磁碟快取Android快取
- 初探Matrix Android ApkCheckerAndroidAPK
- 【css基礎】如何理解transform的matrix()用法CSSORM
- 深入理解Java中的逃逸分析Java
- 深入理解Java中的鎖(一)Java
- 深入理解Java中的鎖(二)Java
- 深入理解Java中的Garbage CollectionJava
- 深入理解python中的yieldPython
- 深入理解JavaScript中的箭頭JavaScript
- 深入理解gradle中的taskGradle
- 深入理解JavaScirpt中的this(轉)Java
- 深入理解JVM中的ClassLoaderJVM
- 深入理解 JavaScript 中的函式JavaScript函式
- 深入理解 JavaScript 中的 replace 方法JavaScript
- Java中的ThreadLocal深入理解Javathread
- 深入理解Android中的快取機制(一)快取簡介Android快取
- 更深入的理解 Python 中的迭代Python
- 更深入的理解Python中的迭代Python
- Android 深入理解 Notification 機制Android
- [深入理解Android卷二 全文-第五章]深入理解PowerManagerServiceAndroid
- android中onMeasure初看,深入理解佈局之一!Android
- 深入理解 Android 之 View 的繪製流程AndroidView