AndroidUI之Matrix

   Matrix在android開發中,特別是一些高級UI的繪製中,做平移,旋轉,縮放,錯切操作很是適用,但是其實Android的API中已經幫我們封裝好了Matrix的很多方法和操作,現在就想記錄一下學習的過程。

   打開Android的Matrix的API(我用的SDK版本是28),首先可以看到9個常量:

    public static final int MSCALE_X = 0;   //!< use with getValues/setValues
    public static final int MSKEW_X  = 1;   //!< use with getValues/setValues
    public static final int MTRANS_X = 2;   //!< use with getValues/setValues
    public static final int MSKEW_Y  = 3;   //!< use with getValues/setValues
    public static final int MSCALE_Y = 4;   //!< use with getValues/setValues
    public static final int MTRANS_Y = 5;   //!< use with getValues/setValues
    public static final int MPERSP_0 = 6;   //!< use with getValues/setValues
    public static final int MPERSP_1 = 7;   //!< use with getValues/setValues
    public static final int MPERSP_2 = 8;   //!< use with getValues/setValues

其實可以猜測,這9個常量可以代表着下面這樣一個3*3 的矩陣

這裏會有一個問題,我們針對的平移,縮放,旋轉,錯切都是針對點的,一般我們在手機中的2D座標(x,y),按照之前學的數學原理來說,矩陣的乘法,必須是前一個舉證的列數,等於後一個矩陣的行數,所以在手機中點的座標爲x,y不能構成矩陣的乘法,所以此時,引入其座標系,增加一個標誌位:(x,y,1)所以這樣纔可以構成矩陣的乘法。上面的一般形式下的Matrix的各個參數表示的意義:

我們在進行進行上述平移,縮放,旋轉,錯切的矩陣變換的時候,其實我們一般不需要關注最下面一行的值,默認值分別位 0 ,0,  1就OK。

Matrix的縮放

設原始座標的齊次座標爲 (x0,y0,1) 縮放的scaleX = k0 ,scaleY = k2, 縮放後的座標 爲(x,y,1)

經過縮放之後得到

                              x = k0 * x0

                              y = k1* y0

在齊次座標系下面(x,y,1)和 (2x,2y,2)...... (nx,ny,n)表示的是同一個座標

Matrix的旋轉

我們一般在使用matrix,進行旋轉變換的時候,一般的都是調用setRotate(degrees) 或者 postRotate(degrees),需要傳遞一個自己的表示角度(角度或是弧度)的參數進去。

上面公式中假設原始座標位(x0,y0,1) 旋轉之後的座標爲(x,y,1)也即是:               

                                                 x  =  x0·cosθ - y0·sinθ

                                                 y =   y0·sinθ - x0·cosθ

Matrix的平移

  用矩陣表示  

計算得到                                                             

                                                                     x  =  x0 +  △x

                                                                     y  =  y0 +  △y

 

Matrix 的 setXXX方法 postXXX 和 preXXX 的關係

      在高等數學中,舉證是滿足結合律,不滿足交換律的,也就是說 假設有三個舉證 A ,B ,C 可以得到

                                    A* B * C  = (A*B)*C  = A* (B*C)

                                    A* B * C  ≠ B*A *C 

所以這裏就造就了舉證的postXXX 和 preXXX setXXX方法的不同:

假設原始矩陣位M   變換矩陣位 S  變換之後的矩陣爲M'   

postXXX方法  表示舉證的 左乘  ,我的記憶方法就是原始矩陣在左邊

                                                        

preXXX方法,表示矩陣的右乘,我的記憶方法就是原始矩陣在 右邊

setXXX方法,當調用set方法的時候,就是覆蓋之前的變換,之前的變換會不起作用

注意:post 也就是 原始矩陣在左邊  pre也就是原始矩陣在右邊 這剛剛與我們平常理解post  和 pre 相反

Matrix.isIdentity方法

      matrix的isIdentity()方法用來判定當前矩陣是否爲單位矩陣,單位矩陣,也就是對角線爲1,1,1的矩陣,如下

Matrix的旋轉

      關於Matrix的旋轉,可以有好幾個方法,對單位矩陣使用的時候沒什麼區別,但是如果對其他非單位矩陣的控件使用,preXXX,postXXX,還有setXXX還是由分別的。

    setRotate(float degrees)                               旋轉degrees的角度

    setRotate(float degrees, float px, float py)    繞點(px,py)旋轉degrees角度

    preRotate(float degrees)                               旋轉degrees角度

    preRotate(float degrees, float px, float py)    繞點px,py旋轉degrees角度

    postRotate(float degrees)                               旋轉degrees角度

    postRotate(float degrees, float px, float py)    繞點px,py旋轉degrees角度

關於矩陣 旋轉的數學推導方法可以借鑑 這篇文章 ,這裏只做一個結論,和效果以便更好的理解關於繞點(px,py)旋轉的意義

2DArbitraryRotate

看圖 可以知道,繞點(px,py)旋轉可以拆分爲 三步走,轉變成平時我們熟悉的繞“座標原點”旋轉:

1,先將目標圖形上的某一點(px,py),也就是我們要圍繞旋轉的點,平移到 座標原點(零點),-----平移操作(-px,-py)。

2.將平移後的圖像,繞座標原點 旋轉degrees度數。     ---旋轉操作

3.將步驟2中得到的圖像,再進行平移操作  ---平移操作 (px,py)

所以setRotate(float degrees, float px, float py)  這個操作可以分成下面三個矩陣相乘 再乘以原始矩陣:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("MatrixView","width/2="+mBitmap.getWidth()/2 + "  height/2="+mBitmap.getHeight()/2);
        Log.e("MatrixView","mMatrix pre rotate="+mMatrix.toShortString());
        mMatrix.setRotate(90,mBitmap.getWidth()/2,mBitmap.getHeight()/2);
        Log.e("MatrixView","mMatrix after rotate="+mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

     

 

左圖是直接調用canvas.drawBitmap直接繪製出來的,matrix是new出來的單位矩陣。

右圖是經過繞bitmap的中心旋轉90°得到的。

04-21 21:36:26.875 9700-9700/? E/MatrixView: width/2=187  height/2=125
04-21 21:36:26.875 9700-9700/? E/MatrixView: mMatrix pre rotate=[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
04-21 21:36:26.876 9700-9700/? E/MatrixView: mMatrix after rotate=[0.0, -1.0, 312.0][1.0, 0.0, -62.0][0.0, 0.0, 1.0]

上面的日誌是打印了bitmpa的中心點的座標 (187,125) ,後面是旋轉之前的單位矩陣 和 旋轉之後的矩陣

上面

             mMatrix.setRotate(90,mBitmap.getWidth()/2,mBitmap.getHeight()/2);

這句代碼 等價於,下面這三句代碼

  mMatrix.postTranslate(-mBitmap.getWidth()/2,-mBitmap.getHeight()/2);
  mMatrix.postRotate(90);
  mMatrix.postTranslate(mBitmap.getWidth()/2,mBitmap.getHeight()/2);

Matrix的平移

     matrix的平移方法setTranslate(float dx,float dy)和canvas的平移沒啥區別,只需要記得向右或者向下平移dx或者dy位正值,向左或者向上平移 dx或者dy爲負值     

Matrix的縮放

     matrix中關於縮放的方法一共有2類,但是和之前講的rotate方法類似:

    setScale(float sx,float sy)                           x 和 y方向分別縮放 sx,sy倍

    setScale(float sx,float sy, float px, float py)    以點(px,py)爲中心  x 和 y方向分別縮放 sx,sy倍

    preScale(float sx,float sy)                                  x 和 y方向分別縮放 sx,sy倍,原始矩陣在前

    preScale(float sx,float sy, float px, float py)         以點(px,py)爲中心  x 和 y方向分別縮放 sx,sy倍,原始矩陣在前

    postScale(float sx,float sy, float px, float py)                            以點(px,py)爲中心  x 和 y方向分別縮放 sx,sy倍,原始矩陣在後

    postScale(float sx,float sy)                         x 和 y方向分別縮放 sx,sy倍,原始矩陣在後

    以(px,py)位中心的縮放,運算原理和上文的平移的矩陣運算原理相同。

Matrix的錯切

 Matrix的setSkew方法也有兩類方法:

setSkew(float kx, float ky)             設置水平方向和豎直方向上的錯切係數
settSkew(float kx, float ky, float px, float py)  設置水平方向和豎直方向上的錯切係數,並且
preSkew(float kx, float ky)
preSkew(float kx, float ky, float px, float py)
postSkew(float kx, float ky)
postSkew(float kx, float ky, float px, float py)

例如下面的代碼

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("MatrixView","mMatrix pre skew="+mMatrix.toShortString());
        mMatrix.setSkew(0.5f,0);
        Log.e("MatrixView","mMatrix after skew="+mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

04-22 22:14:32.843 3532-3532/com.matrix.demo E/MatrixView: mMatrix pre skew=[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
04-22 22:14:32.843 3532-3532/com.matrix.demo E/MatrixView: mMatrix after skew=[1.0, 0.5, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]

水平錯切  

  所謂的水平錯切 也就是在原來點的基礎上,Y座標不變,X座標增加或者減少某一個值,例如上圖Y座標不變,對應的X座標被拉伸

 用矩陣表示就是這樣的。

垂直錯切,就是水平座標不變,Y座標平移一個值:

用矩陣表示爲

還有一種就是符合錯切,就是水平和垂直方向都錯切:

用矩陣表示爲:

上面關於錯切的方法有一類是後面多2個參數float px,float py,這就是以點P(px,py)位錯切中心,進行錯切變換,默認是以原點爲中心,進行錯切變換,看下面代碼:

mMatrix.setSkew(kx,ky,px,py);

以bitmap的中心位P(px,py)進行錯切變換,上面代碼的意思就是,進行錯切變換之後,保持原來的p(px,py)不變,也就是在setSkew(0.1f,0)的錯切變換之後,要將變換之後的p'(px',py') 平移回到原來的p(px,py)點,所以上面的代碼等同於下面兩個變換的操作結果:

mMatrix.postTranslate(-kx*py,-ky*px);
mMatrix.postSkew(kx,ky)

用矩陣表示如下:

Matrix的mapPoints方法

public void mapPoints(float[] pts)
public void mapPoints(float[] dst, float[] src)
public void mapPoints(float[] dst, float[] src)
public void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,int pointCount)

方法大體的意思就是 映射指定的點到指定的集合:

只有一個參數的函數float [] pts,傳入一個偶數數組,分別代表點的x,y座標 經過某種變換(平移,縮放,旋轉,錯切)之後得到結果還是存放在pts中。

多個參數的mapPoint方法:

  • float dst ,存放經過上述某種或者幾種變換之後,將src轉換目標點的數組,原始的src不變
  • int dstIndex,目標數據存儲的起始下標
  • float src  源座標
  • int srcIndex ,源數據起始座標。
  • int pointCount , 數據點的數量....
      // 初始數據爲三個點 (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("MatrixView", "before: src="+ Arrays.toString(src));
        Log.i("MatrixView", "before: dst="+ Arrays.toString(dst));

        // 調用map方法計算(最後一個2表示兩個點,即四個數值,並非兩個數值)
        matrix.mapPoints(dst, 0, src, 2, 2);

        // 輸出計算之後數據
        Log.i("MatrixView", "after : src="+ Arrays.toString(src));
        Log.i("MatrixView", "after : dst="+ Arrays.toString(dst));

04-24 22:11:38.555 2857-2857/com.matrix.demo I/MatrixView: before: src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
04-24 22:11:38.556 2857-2857/com.matrix.demo I/MatrixView: before: dst=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
04-24 22:11:38.557 2857-2857/com.matrix.demo I/MatrixView: after : src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
04-24 22:11:38.557 2857-2857/com.matrix.demo I/MatrixView: after : dst=[40.0, 100.0, 200.0, 300.0, 0.0, 0.0]

Matrix的 invert方法

public boolean invert(Matrix inverse)

反轉當前矩陣,如果能反轉就返回true並將反轉後的值寫入inverse,否則返回false。

      Matrix matrix = new Matrix();
        matrix.setScale(0.5f, 1f);
        Log.e("MatrixView","invert before ="+matrix.toShortString());
        Matrix invert = new Matrix();
        matrix.invert(invert);

        Log.e("MatrixView","invert after ="+invert.toShortString());

會得到下面的log:

04-24 22:19:19.506 2959-2959/? E/MatrixView: invert before =[0.5, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
04-24 22:19:19.506 2959-2959/? E/MatrixView: invert after =[2.0, 0.0, -0.0][0.0, 1.0, -0.0][0.0, 0.0, 1.0]

也就是 當前矩陣M ,逆轉之後的矩陣M' 單位矩陣 I,則有

                                                                          M * M' = I

Matrix的mapRect方法

有下面2個重載方法

public boolean mapRect(RectF rect)  //測量rect並將測量結果放入rect中,返回值是判斷矩形經過變換後是否仍爲矩形。

public boolean mapRect(RectF dst, RectF src)  //測量src並將測量結果放入dst 中,返回值是判斷矩形經過變換後是否仍爲矩形。


        RectF rect = new RectF(400, 400, 1000, 800);

        Matrix matrix = new Matrix();
        matrix.setScale(0.5f, 1f);
        matrix.postSkew(1,0);

        Log.e("MatrixView","mapRect: "+rect.toString());

        boolean result = matrix.mapRect(rect);

        Log.e("MatrixView", "mapRect: "+rect.toString());
        Log.e("MatrixView", "isRect: "+ result);

04-24 22:26:43.884 3260-3260/com.matrix.demo E/MatrixView: mapRect: RectF(400.0, 400.0, 1000.0, 800.0)
04-24 22:26:43.884 3260-3260/com.matrix.demo E/MatrixView: mapRect: RectF(200.0, 400.0, 500.0, 800.0)
04-24 22:26:43.884 3260-3260/com.matrix.demo E/MatrixView: isRect: false

由於進行了錯切變換,所以最後得到的圖形並非矩形。

Matrix的setSinCos方法

public void setSinCos(float sinValue, float cosValue)  //sin的值爲 sinValue cos的值位cosValue
public void setSinCos(float sinValue, float cosValue, float px, float py)  //中心點位(px,py)

這個方法其實和setRotate方法的作用類似,假如setRotate方法傳入的角度爲degrees,則setRotate(degrees)與 setSincos(sin(degrees),cos(degrees))能夠達到同樣的效果,後面float px,float py就是選擇的中心。有了setRotate方法之後,這2個方法基本不怎麼使用。

Matrix的 setPolyToPoly方法

public boolean setPolyToPoly(float[] src, int srcIndex,
        float[] dst, int dstIndex,
        int pointCount)
  • float [ ] src , 要改變的源數據的點的集合,數組長度爲偶數。
  • int srcIndex , 從源數據 的第一個點開始計算
  • float [ ] dst  原始點數據需要變換爲 dst的目標數據
  • int dstIndex ,目標數據的起始位置
  • int pointCount ,需要改變的點的數量

setPolyToPoly的作用是通過多點映射的方式來實現平移 ,縮放,旋轉或者錯切的一種或者多種變換。

pointCount = 0  相當於Matrix.reset操作,將matrix所有變換清空,重置爲單位矩陣

  protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mMatrix.setTranslate(100,100);
        mMatrix.setPolyToPoly(new float[]{0, 0, 400, 0, 400, 400, 0, 400}, 0, new float[]{350, 350, 400,50, 450, 450, 50, 400}, 0, 0);

        Log.e("MatrixView",mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

打印的matrix日誌如下

04-25 21:29:53.656 2413-2413/? E/MatrixView: [1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]

效果如下:

pointCount =1 實現的是一種平移變換,在調用這個方法的時候pointCount必須<=4

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float src [] = {0,0,50,50,100,100};
        float dst [] ={50,50,110,120,130,160};
        mMatrix.setPolyToPoly(src,0,dst,4,1);
        Log.e("MatrixView",mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

04-25 21:12:23.145 1564-1564/com.matrix.demo E/MatrixView: [1.0, 0.0, 130.0][0.0, 1.0, 160.0][0.0, 0.0, 1.0]

上面的方法其和 mMatrix.setTranslate(130,160)是同等的效果。

pointCount = 2 可實現,縮放,旋轉,平移的操作。

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float src [] = {0,0,50,50,100,100};
        float dst [] ={50,50,110,120,130,160};
        mMatrix.setPolyToPoly(src,0,dst,2,2);
        Log.e("MatrixView",mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

初始效果 和 上面得到的效果如下圖  

    

                 初始效果圖                                                                       pointCount =2 時的上例圖

pointCount = 3 可實現,縮放,旋轉,平移,錯切的操作。

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mMatrix.setPolyToPoly(new float[]{0, 0, 400, 0,400,400}, 0, new float[]{0, 0, 400, 0, 500, 400}, 0, 3);

        Log.e("MatrixView",mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

pointCount = 4的時候,可以成爲任意的四邊形

 protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mMatrix.setPolyToPoly(new float[]{0, 0, 400, 0, 400, 400, 0, 400}, 0, new float[]{350, 350, 400,50, 450, 450, 50, 400}, 0, 4);

        Log.e("MatrixView",mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

效果如下:

 

Matrix的setRectToRect方法

public boolean setRectToRect(RectF src, RectF dst, Matrix.ScaleToFit stf)

  • src 座標變換前的矩形
  • dst 座標變換後的矩形
  • stf 矩形縮放選項

由於提供座標變換前後的參數可爲任意矩形,這樣的話,變換前後矩形的長寬比不一定一樣,提供指定Matrix.ScaleToFit選項來確定縮放選項。Matrix.ScaleToFit定義了四種選項:

  • CENTER: 保持座標變換前矩形的長寬比,並最大限度的填充變換後的矩形。至少有一邊和目標矩形重疊。
  • END:保持座標變換前矩形的長寬比,並最大限度的填充變換後的矩形。至少有一邊和目標矩形重疊。END提供右下對齊。
  • FILL: 可能會變換矩形的長寬比,保證變換和目標矩陣長寬一致。
  • START:保持座標變換前矩形的長寬比,並最大限度的填充變換後的矩形。至少有一邊和目標矩形重疊。START提供左上對齊。

google的API demo裏面的效果圖如下

 

到此爲止,Martix中的大部分API都講解記錄的差不多了,這些api在自定義View中,都很有用。如有哪裏寫的有問題,歡迎留言。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章