Android Matrix 帶你掌控雷電
Matrix 是什么
Matrix 擁有一個 3 * 3 矩陣,這個矩陣用于坐標變換。這個矩陣定義如下
Matrix.PNG
Matrix 操作圖像的 API 有平移(Translate),縮放(Scale),旋轉(Rotate),錯切(Skew),然而并沒有對透視(Perspctive)的操作,Google 了關于 Matrix 的文章,MPERSP_0, MPERSP_1, MPERSP_2 分別為0,0,1,至于為什么,簡單來看,就是為了用一個通用矩陣表示以上四種變換。
下面先從數學角度推導下四種變換對應的 Matrix 是什么。
平移的矩陣
一個點 P 平移到 P' ,在坐標系表示如下
Translate.PNG
用數學公式表示
Translate_Math.PNG
轉換為坐標系表示
Translate_Matrix1.PNG
中間的3 * 3 矩陣就是平移對應的 Matix , ?x 就是 x 軸的增量,?y 就是 y 軸的增量。
旋轉矩陣
旋轉是有中心點的,如果把 Matrix 應用到圖片,默認旋轉點是圖片的左上角,也就是圖片的坐標原點(0,0)。
繞坐標原點旋轉矩陣
Rotate_XY.PNG
用數學公式表示
Rotate_Math.PNG
用矩陣表示
Rotate_Matrix1.PNG
中間的 3 * 3 矩陣就是繞坐標原點旋轉的 Matrix。
繞非坐標原點旋轉矩陣
如果圖片并非繞它的坐標原點(左上角)旋轉,而是繞任意點旋轉,例如 圖片中心點 (x1, y1),那么這個又如何計算呢?
這里我們先用數學思維思考,我們可以把(x1,y1) 作為坐標原點,那么 P 的表示應該為 (x0-x1, y0-y1),P' 的坐標為 (x-x1, y-y1),那么我們再應用旋轉點是坐標原點的矩陣公式,就應該這樣寫
Rotate_Matrix1.PNG
再簡化下
Rotate_Matrix2.PNG
是不是現在感覺比較明朗,等式右邊第一個和第三個矩陣就是平移矩陣,我們在后面會看到如何生成這三個矩陣的結果。
我們再來理解下這個矩陣,最后兩個矩陣相乘代表坐標原點移動到(x1,y1),最后三個矩陣代表以(x1, y1)為原點旋轉,最后四個矩陣代表坐標原點從(x1, y1)移動到(0,0),這樣是不是好記一些。
縮放矩陣
圖片都是由像素點構成的,如果一個圖片放大 k 倍,可以看作每個點的 x 和 y 坐標值放大 k 倍。當然縮放也有中心點的。圖片默認的縮放中心為圖片的左上角,也就是圖片的原點(0,0)。
原點為中心的縮放
用數學表示如下
Scale_Math.PNG
用矩陣表示如下
Scale_Matrix.PNG
中間的矩陣就是繞原點綻放的 Matrix
非原點的縮放
這與非原點的旋轉的數學思維是一樣的,這里直接給出矩陣
Scale_Matrix1.PNG
中間的三個矩陣相乘就是非原點縮放 Matrix
錯切矩陣
錯切分為 x 軸和 y 軸的錯切。
x 軸錯切矩陣
Skew_X.PNG
x 軸錯切,是保持坐標的 y 軸值不變,x 軸值的做線性變換 ,表示如下
Skew_Math.PNG
斜率為 1/k
矩陣表示如下
Skew_MatrixX.PNG
矩陣表示用到的是 k,而不是斜率 1 / k,因此 k 越大,圖形錯切的越大。
y 軸錯切矩陣
Skew_Y.PNG
y軸錯切,就是 x 軸的值不變,y 軸的值做線性變換,矩陣就不用我再推理吧,表示如下
Skew_MatrixY.PNG
x 軸 y 軸的錯切矩陣
綜合 x 軸 和 y 軸錯切,統一表示如下
Skew_MatrixXY.PNG
kx 表示 x 軸的錯切值,ky 表示 y 軸的錯切值。kx,ky 越大,圖形錯切的越大。
看完了可惡的數學公式,我們就懂得了原理 ,現在用 Matrix 的 API 來測試測試吧。
每種變換都有 setXx() ,postXx(),preXx() 方法來設置相應的變換 。如 Translate,有 Matrix.setTranslate(),Matirx.postTranslate(),Matrix.preTranslate()。我將會在代碼中解釋這些到底怎么用,請大家多注意,因為大部分人會用錯。
默認顯示一個 launcher 圖標
我們先顯示一個不做 Matrix 處理的圖標
/**
- Created by David Chow on 2016/12/6.
*/
public class MatrixView extends View {
private Matrix mMatrix;
private Bitmap mBitmap;
public MatrixView(Context context) {
this(context, null);
}
public MatrixView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
mMatrix = new Matrix();
Log.d("david", mMatrix.toString());
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBitmap, mMatrix, null);
}
}</code></pre>

Default.PNG
我們看看這個初始的 Matrix 矩陣是什么
Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]}
這就是一個 3 * 3 單位矩陣 ,因此它不論前乘還是后乘都無所謂。
平移
@Override
protected void onDraw(Canvas canvas) {
mMatrix.setTranslate(200, 200); // 單位 pixel
canvas.drawBitmap(mBitmap, mMatrix, null);
Log.d("david", "translate matrix : " + mMatrix.toString());
}

Translate.PNG
我們看到了圖像平移了,這個時候,我們打印 Log,可以看到矩陣的值為
translate matrix : Matrix{[1.0, 0.0, 200.0][0.0, 1.0, 200.0][0.0, 0.0, 1.0]}
我們可以看到 Matrix.setTranslate() 把單位矩陣重置為 平移的矩陣(上面數學分析得出的平移矩陣)
那么用理論的的矩陣這樣表示

Translate.PNG
這樣與我們上面理論是不是就一致了,這個時候我們心情是不是稍微好點了,因為我們理論終于在實際中得到驗證了。后面我們將不再去這個用矩陣來驗證理論,我們只打印相應的 Log 即可。
縮放
@Override
protected void onDraw(Canvas canvas) {
mMatrix.setTranslate(200, 200); // 單位 pixel
mMatrix.setScale(3, 3);
canvas.drawBitmap(mBitmap, mMatrix, null);
Log.d("david", mMatrix.toString());
}
Log打印如下
Matrix{[3.0, 0.0, 0.0][0.0, 3.0, 0.0][0.0, 0.0, 1.0]}
就算我們先用了一個無關的 Matrix.setTranslate() , 后面的 Matrix.setScale() 還是會重置矩陣為縮放的矩陣。這點大家要記住,免得以后沒有達到效果,卻不知道問題在哪里了。

Scale.PNG
默認的縮放中心是圖片的左上角,也就是(0,0),當然我們也可以調整縮放中心位置
@Override
protected void onDraw(Canvas canvas) {
mMatrix.setScale(3, 3,mBitmap.getWidth()/2,mBitmap.getHeight()/2);
canvas.drawBitmap(mBitmap, mMatrix, null);
Log.d("david", mMatrix.toString());
}
現在縮放中心的位置為 Bitmap 的中心位置,現在看下效果

Scale1.PNG
從效果看,確實是根據中心點縮放,以致圖片的左上角超出了屏幕顯示。
我們打印Log看下
Matrix{[3.0, 0.0, -126.0][0.0, 3.0, -126.0][0.0, 0.0, 1.0]}
那么與我們上面理論分析是不是相符,就留給大家去驗證了。
Matrix.setRotate()旋轉
旋轉也是有中心點的,先看看個繞圖片中心旋轉的情況
@Override
protected void onDraw(Canvas canvas) {
mMatrix.setRotate(45, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
canvas.drawBitmap(mBitmap, mMatrix, null);
}
Log打印如下
Matrix{[0.70710677, -0.70710677, 63.0][0.70710677, 0.70710677, -26.095451][0.0, 0.0, 1.0]}
驗證理論還是交給大家了~~

Rotate_Default.png
當然默認的旋轉中心也是圖片的左上角
@Override
protected void onDraw(Canvas canvas) {
mMatrix.setTranslate(200, 200); // 單位 pixel
mMatrix.setScale(3, 3);
mMatrix.setRotate(30);
canvas.drawBitmap(mBitmap, mMatrix, null);
Log.d("david", mMatrix.toString());
}
同樣,setRotate() 覆蓋了前面的 setScale(), setTranslate()
打印下矩陣信息
Matrix{[0.8660254, -0.5, 0.0][0.5, 0.8660254, 0.0][0.0, 0.0, 1.0]}
根據我們上面講的,這個里面的小數值 ,對應角度為 30 的 sin 或者 cos 值,大家可以自己用計算器算算~~
現在看下效果圖

Rotate.PNG
我們發現這個移出了屏幕了,因此現在我們來平移下
@Override
protected void onDraw(Canvas canvas) {
mMatrix.setTranslate(200, 200); // 單位 pixel
mMatrix.setScale(3, 3);
mMatrix.setRotate(30);
mMatrix.preTranslate(200, 200);
canvas.drawBitmap(mBitmap, mMatrix, null);
Log.d("david", mMatrix.toString());
}

Rotate_Translate_Error.PNG
一個奇怪的現象就產生了,X 方向的平移呢?其實這是因為矩陣乘法不滿足交換律的原因。 mMatrix.preTranslate() 意思是用 mMatrix 矩陣前乘 translate 矩陣,數據表示如下

Rotate_Translate.png
這個結果就不用我算了吧,很顯然實際偏移的并不是200,200。那么我們如何讓它既旋轉30°,又正常偏移200,200呢。 我們可以讓 mMatrix 后乘 translate
@Override
protected void onDraw(Canvas canvas) {
canvas.drawLine(0, 0, 200, 200, mPaint);
mMatrix.setRotate(30);
mMatrix.postTranslate(200, 200);
canvas.drawBitmap(mBitmap, mMatrix, null);
}
為了看到我們確實是偏移了200,200,我畫了一條紅色的線

Rotate_PostTranslate.png
那么我們用數學來表達下

Post_Translate.png
大家計算下,是不是既旋轉了又平移了200,200?
我用這個例子是為了讓大家理解 preXx() 和 postXx() 的區別,希望大家自己動手試試加深理解。
再給大家一個 Tip,如果想要平移達到效果,最后調用 postTranslate()。
Matrix.setSkew() 錯切
先用默認中心點,即圖片的左上角進行錯切。為了看到效果,我們先平移200,200,再進行錯切變換
mMatrix.setTranslate(200, 200);
mMatrix.preSkew(1, 0);
canvas.drawBitmap(mBitmap, mMatrix, null);

Skew.png
這里我并沒有遵循上面的例子最后用 postTranslate(),這是因為根據矩陣的特性,這樣剛好不影響平移,我們可以打印Log看看矩陣
Matrix{[1.0, 1.0, 200.0][0.0, 1.0, 200.0][0.0, 0.0, 1.0]}
我沒說錯吧,但是如果你再操作下,就有問題了,例如例如 postScale(3,3),就會平移 600,600了。當然如果你最后還是調用 postTranslate(200,200) ,會正常平移400,400。
大家別看我說的很簡單,自動寫的時候就會遇到各種問題,因此大家在看的時候還是多動手。
再看看中心點不是原點的錯切,例如用圖片的左下角來錯切。
@Override
protected void onDraw(Canvas canvas) {
mMatrix.setTranslate(200, 200);
mMatrix.preSkew(1, 0, 0, mBitmap.getHeight());
canvas.drawBitmap(mBitmap, mMatrix, null);
Log.d("david", mMatrix.toString());
}
打印Log
Matrix{[1.0, 1.0, 74.0][0.0, 1.0, 200.0][0.0, 0.0, 1.0]}
理論驗證繼續留給大家了~~
思考
我們使用 Matrix API 的時候,我們只是一名 Developer,但是我們要把自己當做一個 Designer,我們思考下,其實我們可以利用矩陣做很多想要的效果,因為我們可以用 Matrix.setValues() 來設置自己想要的矩陣 ,因此我們可以設計對稱效果,倒影效果等等,這就要大家去發掘啦~~
結束
這篇文章大家入門 Matrix,當然這是為了我后面文章打基礎的,還是那句話,多動手,如果遇到問題解決不了,歡迎大家留言討論。 如果大家覺得還不錯,可以點個贊,甚至來一波關注不惜留戀_, what's a nice day~~
來自:http://www.jianshu.com/p/b8cc4ac9780b