前言
在上一篇文章中,我們講解了 Canvas 中單個變換的原理和效果,即縮放、旋轉和平移。但是單個旋轉僅僅是基礎,Canvas 變換最重要的是能夠隨意組合各種變換以實現想要的效果。在這種情況下,就需要了解如何組合變換,以及組合變換背后的矩陣是如何計算出來的。
閱讀本文之前,建議先閱讀:深度解析 Android Matrix 變換(一):縮放 scale、旋轉 rotate、平移 translate
在這篇文章中,我就帶領大家理解組合變換以及其背后的原理。通過這篇文章,大家肯定能隨意組合變換以達到想要的效果。
從一個點開始
在二維平面中,表示一個點只是需要其 x,y 坐標,但是在表換過程中,并不是使用兩個值的表示方式,而是用齊次坐標來表示。簡單來說,就是 x,y 坐標再加上一個 1 來表示。當用列向量來表示這個點的坐標時,因為其具有三行,因此可以與各種變換的矩陣相乘,因為矩陣都是 3*3
的,可以與 3*1
的向量相乘。
我們用 Ms 表示縮放矩陣,Mr 表示旋轉矩陣,Mt 表示平移矩陣,如果對于一個點 P0,我們對它依次應用旋轉平移和縮放,那么變換后得到的點 P1 為:
P1 = Ms * Mt * Mr * P0
注意相乘的順序,我們必須從又往左相乘,但是由于矩陣乘法滿足結合律,因此這三個矩陣可以合并為一個與 P0 相乘。
在了解了如何組合矩陣之后我們就從代碼層面理解如何構建想要的組合矩陣。
構建矩陣
在上篇文章中,我們介紹過,縮放旋轉和平移都有對應的 post 和 pre 版本。在這一節中,我們來介紹這兩個種類的方法的具體意義:
- Matrix.preScale(float sx, float sy);
- Matrix.postScale(float sx, float sy);
- Matrix.preRotate(float degrees);
- Matrix.postRotate(float degrees);
- Matrix.preTranslate(float dx, float dy);
- Matrix.postTranslate(float dx, float dy);
在上面的方法中,我們先過濾掉 preXXX
和 postXXX
的方法名和參數,因為這些方法實際上都是通過方法名和參數來構建一個矩陣,然后 pre 或者 post 這個矩陣。也就是說,上述的方法可以總結為兩個方法:
- Matrix.preConcat(Matrix other)
- Matrix.postConcat(Matrix other)
那么這兩個方法有什么區別呢?說簡單一點就是 pre 是左乘,post 是右乘。舉個例子:
A.preConcat(B)
表示 A * B 結果設置到 A 中;A.preConcat(B)
表示 B * A 結果設置到 A 中;
變換順序
在構建矩陣時,變換順序是非常重要的。上面只是對變換時矩陣的乘法進行了講解,但是在實際編碼時,我們通常是按照 先縮放,再旋轉,最后平移 的順序來進行的。
對于一個變換,我們必須考慮按照這個順序來完成,特別是平移需要最后完成。否則后續的旋轉或縮放會影響平移變換,變換的結果可能完全不同,甚至不符合直覺。
而按照這個變換做的好處是:
- 縮放發生在局部坐標系,不影響旋轉或平移的方向。
- 旋轉發生在縮放之后,不會影響縮放比例。
- 平移最后執行,不會受到縮放或旋轉的干擾。
實例
最后我們組合一個變換,并通過動畫的形式演示變換過程。
我們還是以上篇文章的圖片為基礎:
我們先對這個圖片進行縮放(1.5,0.5),再旋轉30度,最后移動(500, 200)。
根據上面對構建矩陣的描述,我們通過下面的代碼來生成變換矩陣:
Matrix matrix = new Matrix();
matrix.postScale(1.5F, 0.5F);
matrix.postRotate(30);
matrix.postTranslate(500, 200);
在將這個 matrix 應用到 Canvas 后,可以看到變換后的結果:
其實,對于某一個結果,可以通過多種方式來構造變換矩陣。例如這個變換,我們還可以通過下面的矩陣生成:
Matrix matrix = new Matrix();
matrix.setRotate(30);
matrix.preScale(1.5F, 0.5F);
matrix.postTranslate(500, 200);
既然同一個變換有多種構建方式,那么應該用哪種構建矩陣的方式呢?這里還是建議用復雜的方式,至少,別人越是看不懂,越是顯得你的重要性。
最后,通過動畫,來演示一下這個組合變換是如何生效的: