安卓自定義View——Matrix

一、Matrix簡介

  1. Matrix是一個矩陣,主要功能映射視圖中的座標,這裏的映射有兩重意思,一是將座標以矩陣的形式表示,相似於數學中的向量,二是映射手機屏幕座標和安卓座標的對應關係,在物理屏幕中座標原點爲左上角(0,0)處,而在Android了開發中使用的座標是去除狀態欄和導航欄的高度,即(0,0)位置在狀態欄之下,假設狀態欄30px,導航欄60px,則Android中的(0,0)對應物理座標(0,90)
  2. 矩陣除了映射座標外還存在另外一個做用,數學中都學過矩陣的變換,經過矩陣變換實現向量的修改,一樣思想若是把整個變換做用於某個圖形的全部像素,則能夠實現圖形的變換,這就是Matrix的另外一個功能,實際他一直在默默的作事情

二、Matrix的矩陣形式

  • 矩陣公式:View的屬性及其改變均可以映射其中

  1. MSCALE_X、MSACALE_Y:表明View的X、Y座標的縮放係數;
  2. MTRANS_X、MTRANS_Y:表明View的X、Y的方向的位移量;
  3. MSKEW_X、MSKEW_Y:表明View的X、Y的方向的位側錯切;
  4. MSCALE_X、MSACALE_Y、MSKEW_X、MSKEW_Y:共同決定View的旋轉;
  5. MPERSP用於控制透視,一般爲(0,0,1)


View的改變和動畫執行的背後都是在作Matrix的計算,經過必定規則改變Matrix矩陣的值,將改變後的數據反應在視圖上就行成了View的展現,因此在自定義View的過程當中能夠將不少關於座標和動畫的過程用Matrix實現,不過安卓提供了不少方法簡化了功能,將Matrix推至幕後工做。canvas

  • 矩陣計算規則
  1. 矩陣乘法不知足交換律,即 A*B ≠ B*A
  2. 矩陣乘法知足結合律,即 (A*B)*C = A*(B*C)
  3. 矩陣與單位矩陣相乘結果不變,即 A * I = A

三、使用Matrix實現View動畫數組

  • 縮放

縮放反應在座標上的效果就是每一個像素的座標(x,y)按照必定規則變大或縮小即造成縮放效果,每一個點的計算公式爲:bash

x = k1 * x0 
Y = k2 * y0 複製代碼

根據變換公示就是將座標(x、y、z)分別乘以縮放係數,若是按照上面的矩陣MSCALE_X、MSACALE_Y表示縮放,將乘積關係使用矩陣表示爲原座標矩陣 X 縮放矩陣,其實這裏的就是數學中的矩陣知識,看到最後的結果便可退到出所乘的矩陣app

  • 位移

位移在座標上的體現就是在同方向上全部的點的座標同時加上或減小相同的數據,實現試圖的總體平移,公式中都只是在基礎上加上移動的參數,由MTRANS_X、MTRANS_Y控制,矩陣乘積以下:ide


  • 旋轉

旋轉相對意位移和縮放來講複雜一點,但也是有章可循,試想一下若是個你一個直線或一個點,旋轉必定角度後計算此時的座標位置,相信全部人都會想到利用正弦和餘弦函數,以旋轉不變的長度爲半徑便可算出(x,y),這也是Matrix控制旋轉的原理,公式以下:函數

上面的過程就是公示變換,公式忘記的能夠自行百度,公式轉換的矩陣:post

  • 錯切

錯切:其實當第一次聽到它時徹底想像不出是什麼效果,可看了效果圖後發現名字很形象,就是控制一個座標的位置不變,而後修改另外一軸的座標,效果上造成被一個角平行拉動的效果優化

x = x0 + k * y0
Y = y0複製代碼

矩陣表示動畫

三、Matrix的方法和使用

  • preXxx():矩陣前乘 M * S ( 至關於矩陣右乘),經過乘積獲得改變結果矩陣,系統中提供了集中封裝好的處理方法
  1. preTranslate(float dx, float dy):矩陣位移,修改單元矩陣中MTRANS_X、MTRANS_Y後相乘
  2. preScale(float sx, float sy):縮放功能;右乘縮放矩陣
  3. preRotate(float degrees, float px, float py):旋轉功能;乘旋轉矩陣
  4. preSkew(float kx, float ky):錯切;乘錯切矩陣
val bitmap = BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)
canvas?.drawBitmap(bitmap, matrix, paint) //繪製基本的Bitmap

canvas?.translate(0f, 200f)
val matrix = Matrix()
matrix.preScale(2f,2f) //縮放2倍
canvas?.drawBitmap(bitmap, matrix, paint)

canvas?.translate(0f, 300f)
val matrixT = Matrix()
matrixT.preRotate(90f) //旋轉90度
canvas?.drawBitmap(bitmap, matrixT, paint)

canvas?.translate(0f, 300f)
val matrixR = Matrix()
matrixR.preTranslate(100f,0f) //位移
canvas?.drawBitmap(bitmap, matrixR, paint)

canvas?.translate(0f, 300f)
val matrixS = Matrix()
matrixS.preSkew(0.5f,0f) //X軸方向錯切
canvas?.drawBitmap(bitmap, matrixS, paint)複製代碼

使用效果ui

  • postXxx():矩陣後乘 S * M(至關於左乘)
  1. postTranslate(float dx, float dy):位移
  2. postScale(float sx, float sy):縮放
  3. postRotate(float degrees, float px, float py):旋轉
  4. postSkew(float kx, float ky):錯切
  5. 使用方法和上面Pre一致
  • 組合使用
  1. 使用post和pre能夠實現一樣的效果,具體效果取決最終的表達式;
val matrix = Matrix()
matrix.postScale(2f, 2f) // S * M
matrix.postRotate(100f)  // R * ( S * M ) = R * S * M = R * S



val matrix = Matrix()
matrix.preRotate(100f) //  M * R
matrix.preScale(2f,2f) // M * R * S = R * S (與上面一致)複製代碼
  1. 全部的操做(旋轉、平移、縮放、錯切)默認都是以座標原點爲基準點的,若是想指定原點須要藉助平移實現
  2. Matrix的操做是改變座標系的,每次操做的座標系狀態會保留,而且影響到後續狀態,若是不須要對後需的操做影響,須要重置Matrix
  • 實現基於設置的某一點旋轉
  1. 旋轉默認中心爲座標起點,如今咱們想設定能夠圍繞制定點(x,y)旋轉,分析一下實現的過程,首先將座標以(x,y)爲原點這樣默認的中心就是(x,y)即須要將座標系位移(x,y),而後進行旋轉,此時的View是以(x,y)中心旋轉,旋轉後此時的座標系與開始相比位移了(x,y),此時只需平移回去便可,按照此流程程序的僞代碼應該爲: M * T * R * (-T) = T * R * -T
Matrix matrix = new Matrix();
matrix.preTranslate(pivotX,pivotY);
matrix.preRotate(angle);
......
matrix.preTranslate(-pivotX, -pivotY);複製代碼
  1. 上面的寫法雖然實現克功能,但將對座標系平移的操做放在了兩端,中間夾雜着轉換操做,使用post變換寫法使轉換和座標系的平移分離,代碼以下,效果同樣:T * R * -T
Matrix matrix = new Matrix();

// 各類操做,旋轉,縮放,錯切等,能夠執行屢次。

matrix.postTranslate(pivotX,pivotY);
matrix.preTranslate(-pivotX, -pivotY);複製代碼
  • pre和post使用注意點:
  1. pre和post均可以實現一樣效果,矩陣最終的效果取決於最後的乘積順序,但使用時要注意儘可能先選擇一種設置好效果後,在根據場景優化便可;
  2. 上面的各類pre和post時針對單元矩陣操做的,因此能夠有不少一樣效果的變換,但當操縱不是單元矩陣時要當心對待,矩陣不知足交換律
  • equals():比較兩個Matrix的數值是否相同
  • toString():將Matrix轉換爲字符串(對象集合的形式輸出)
  • toShortString():將Matrix轉換爲字符串
Log.e("=======",matrix.toString())
Log.e("=======",matrix.toShortString())

2019-04-09 13:11:05.292 12097-12097/? E/=======: Matrix{[2.0, 0.0, 0.0][0.0, 2.0, 0.0][0.0, 0.0, 1.0]}
2019-04-09 13:11:05.292 12097-12097/? E/=======: [2.0, 0.0, 0.0][0.0, 2.0, 0.0][0.0, 0.0, 1.0]複製代碼
  • set(Matrix src):設置新的Matrix
  • reset():重置Matrix
  • setValues(float[] values):至關於賦值Matrix,根據提供的集合提取數據到Matrix中;
  1. values數據長度必須大於等於9,截取前面9個數據爲Matrix
val values = floatArrayOf(2.0f, 0.0f, 0.0f,0.0f, 2.0f, 0.0f,0.0f, 0.0f, 1.0f)
matrix.setValues(values)

//效果至關於:matrix.preScale(2f,2f)複製代碼
  • getValues(float[] values):獲取Matrix中的數值保存在values中,與setValues()相對應
  • mapPoints:數值計算
  1. void mapPoints (float[] pts):將傳入的參數pts經Matrix變換後保存在pts中
  2. void mapPoints (float[] dst, float[] src):將傳入的參數src經Matrix變換後保存在dst中
  3. void mapPoints (float[] dst, int dstIndex,float[] src, int srcIndex, int pointCount):可指定一步分數據進行運算
float[] pts = new float[]{0, 0, 80, 100, 400, 300};
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f);  //將X軸座標縮小一倍
matrix.mapPoints(pts); //輸出pts中結果:[0.0, 0.0, 40.0, 100.0, 200.0, 300.0]複製代碼
  • mapRect():將rectF中數據計算並將結果保存在Matrix中,並斷定是否爲矩形,矩形返回true,不然返回false
RectF rect = new RectF(400, 400, 1000, 800);
boolean result = matrix.mapRect(rect);

複製代碼
  • mapVectors:測量向量
  1. 使用方式與mapPoints()一致,只是mapVectors不受位移影響(位移時向量的矢量不變)
float[] src = new float[]{1000, 800};
float[] dst = new float[2]; 

Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f);
matrix.postTranslate(100,100); 

matrix.mapVectors(dst, src); //輸出結果:[500.0, 800.0] ,平移無效
Log.i(TAG, "mapVectors: "+Arrays.toString(dst)); 

matrix.mapPoints(dst, src); //輸出結構:[600.0, 900.0]
Log.i(TAG, "mapPoints: "+Arrays.toString(dst));複製代碼
  • setPolyToPoly():設置多邊形變換,方法不太好理解,簡單的說就是經過setPolyToPoly()將試圖的幾個點訂(圖釘)在某個位置,其他未操做的點是能夠自由隨之變更的
  1. boolean setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount):參數

(1)src:原始數組 src [x,y],存儲內容爲一組點

(2)srcIndex:原始數組開始位置

(3)dst:目標數組 dst [x,y],存儲內容爲一組點

(4)dstIndex:目標數組開始位置

(5)pointCount:控制點數量:0~4;設置控制點越多,則可操做性越大

  • pointCount最多控制4個:

(1)控制一個點:一張紙一個圖釘智能實現評移效果,訂在哪是哪

(2)控制兩個點:實現旋轉功能;一張紙兩個釘能夠將紙斜着釘

(3)控制三個點:實現錯切

(4)控制四個點:千奇百怪的圖形

  • 使用實例(控制四個點,將三個點保持不變,而後修改第四個點位置,圖形隨之改變)
bitmap = BitmapFactory.decodeResource(context?.resources, R.drawable.image)

val src = floatArrayOf(0f,0f,  //原始定點位置數組
        bitmap!!.width.toFloat(),0f,
        bitmap!!.width.toFloat(),bitmap!!.height.toFloat(),
        0f,bitmap!!.height.toFloat())

val dst = floatArrayOf(0f,0f,
        bitmap!!.width.toFloat(),0f,
        bitmap!!.width.toFloat() - 1500 ,bitmap!!.height.toFloat() - 800,  //修改目標集合的座標值
        0f,bitmap!!.height.toFloat())

matrixPaint.setPolyToPoly(src,0,dst,0,4)   //設置源數據集合、目標集合、控制點個數
matrixPaint.postScale(0.3f,0.3f)   // 設置縮放係數複製代碼
  1. 使用效果

  • setRectToRect( RectF src, RectF dst, ScaleToFit stf )
  1. 將內容從原數據區域填充到目標區域並設置填充模式
  2. 提供四種填充模式:CENTER、START、END、FILL
  • 矩陣方法
  1. invert :求逆矩陣(乘積未單元矩陣)
  2. isIdentity:判斷矩陣是否爲單位矩陣

四、Matrix Camera

  • 做用:使用Camera和3D座標系構創建體效果
  • 3D座標系:座標系使用中須要注意一下兩點
  1. Camera座標系採用左手座標系,X軸正向向右,Y軸正向向上,Z軸垂直屏幕向裏;
  2. Camera的座標系和Matrix及Canvas座標系Y軸方向相反,Y軸向上爲正向
  • 三維投影
  1. 正交投影:正視圖、側視圖、俯視圖
  2. 透視投影:當View和攝像機在同一直線上時,沿Z軸位移的效果:近大遠小、當View和攝像機不在同一直線上時,沿Z軸位移的效果:在縮小的同時靠近攝像機的投影位置(視線相交)
  • 經常使用方法
  1. camera.save():保存Camera狀態,使用方法和Canvas.save()同樣,在執行操做前保存Camera
  2. camera.retore():回滾Camera狀態
  3. getMatrix():計算Camera當前狀態下的矩陣狀態,並將計算結果賦值到Matrix中
  4. applyToCanvas():計算Camera當前狀態下的矩陣狀態,並將計算結果賦值到Canvas中
  • translate (float x, float y, float z):平移
  1. X軸方向平移,同向效果一致
camera.translate(x, 0, 0); 
matrix.postTranslate(x, 0);複製代碼
  1. X軸方向平移,反向效果相反
Camera camera = new Camera();
camera.translate(0, 100, 0);   //Camera沿Y軸正向位移100,向上

Matrix matrix = new Matrix();
camera.getMatrix(matrix);  //獲取當前位置的矩陣信息(移動後的信息)
matrix.postTranslate(0,100); //Matrix沿Y軸正向位移100 (互相抵消,回到原點)複製代碼
  1. Z軸平移

(1)、當View和攝像機在同一直線上時,沿Z軸位移的效果:近大遠小

(2)、當View和攝像機不在同一直線上時,沿Z軸位移的效果:在縮小的同時靠近攝像機的投影位置(視線相交)

  • void rotate (float x, float y, float z):旋轉
  1. 旋轉中心默認爲座標原點(圖片左上角),可以使用Matrix改變旋轉中心
  2. 提供3種直接旋轉方法
void rotateX (float deg);
void rotateY (float deg);
void rotateZ (float deg);

matrix?.postTranslate(centerX.toFloat(), centerY.toFloat()) //利用Matrix設置旋轉的中心
matrix?.preTranslate(-centerX.toFloat(), -centerY.toFloat())複製代碼
  • setLocation (float x, float y, float z):設置相機位置(默認 0,0,-8)
  1. 相機和View的z軸距離不能爲0,不然沒法體現出Z軸效果,正如一葉障目不見泰山同樣
  2. 攝像機平移一個單位等於View平移72個像素
  3. 攝像機右移等於View左移
camera.setLocation(1,0,-8);
camera2.translate(-72,0,0);  //兩者相等複製代碼
  • 實現三維旋轉
  1. 分析實現思路:繼承Animation,在處理動畫中根據差值器的執行計算當前的旋轉角度,使用camera執行旋轉動畫和位移動畫,最後設置座標系的平移和恢復
class RotateAnimation : Animation {

    var centerX = 0 //設置旋轉的中心座標
    var centerY = 0
    var fromDegree = 0f //設置開始和結束的角度

    var toDegree = 0f //設置旋轉的角度
    var translateZ = 0f //Z軸旋轉
    var currentDegree = 0f //當前角度

    val camera = Camera()
    var matrix: Matrix? = null

    constructor(centerX: Int, centerY: Int, fromDegree: Float, toDegree: Float, translateZ: Float) {
        this.centerX = centerX
        this.centerY = centerY
        this.fromDegree = fromDegree
        this.toDegree = toDegree
        this.translateZ = translateZ
    }

    override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
        super.applyTransformation(interpolatedTime, t)
        //根據差值器執行進度計算當前角度
        currentDegree = (toDegree - fromDegree) * interpolatedTime + fromDegree 
        matrix = t?.matrix //獲取此時旋轉的矩陣信息

        camera.save()
        camera.translate(0f, 0f, translateZ * interpolatedTime) //設置旋轉時的Z軸位移
        camera.rotateY(currentDegree) //設置旋轉角度
        camera.getMatrix(matrix)
        camera.restore()

       //利用Matrix設置旋轉的中心
        matrix?.postTranslate(centerX.toFloat(), centerY.toFloat()) 
        matrix?.preTranslate(-centerX.toFloat(), -centerY.toFloat())
    }
}複製代碼
  1. 使用和執行效果
imageView.setOnClickListener {
            val centerX = imageView.width / 2
            val height = imageView.height / 2

            val animation = RotateAnimation(centerX,height,0f,180f,50f)
            animation.duration = 3000
            animation.fillAfter = true
            imageView.startAnimation(animation)
        }複製代碼
  1. 執行效果

相關文章
相關標籤/搜索