1. 程式人生 > >安卓自定義View進階-Matrix詳解

安卓自定義View進階-Matrix詳解

這應該是目前最詳細的一篇講解Matrix的中文文章了,在上一篇文章Matrix原理中,我們對Matrix做了一個簡單的瞭解,偏向理論,在本文中則會詳細的講解Matrix的具體用法,以及與Matrix相關的一些實用技巧。

⚠️ 警告:測試本文章示例之前請關閉硬體加速。

Matrix方法表

按照慣例,先放方法表做概覽。

方法類別 相關API 摘要
基本方法 equals hashCode toString toShortString 比較、 獲取雜湊值、 轉換為字串
數值操作 set reset setValues getValues 設定、 重置、 設定數值、 獲取數值
數值計算 mapPoints mapRadius mapRect mapVectors 計算變換後的數值
設定(set) setConcat setRotate setScale setSkew setTranslate 設定變換
前乘(pre) preConcat preRotate preScale preSkew preTranslate 前乘變換
後乘(post) postConcat postRotate postScale postSkew postTranslate 後乘變換
特殊方法 setPolyToPoly setRectToRect rectStaysRect setSinCos 一些特殊操作
矩陣相關 invert isAffine(API21) isIdentity 求逆矩陣、 是否為仿射矩陣、 是否為單位矩陣 …

Matrix方法詳解

構造方法

構造方法沒有在上面表格中列出。

無參構造

Matrix ()

建立一個全新的Matrix,使用格式如下:

Matrix matrix = new Matrix();

通過這種方式創建出來的並不是一個數值全部為空的矩陣,而是一個單位矩陣,如下:

有參構造

Matrix (Matrix src)

這種方法則需要一個已經存在的矩陣作為引數,使用格式如下:

Matrix matrix = new Matrix(src);

建立一個Matrix,並對src深拷貝(理解為新的matrix和src是兩個物件,但內部數值相同即可)。

基本方法

基本方法內容比較簡單,在此處簡要介紹一下。

1.equals

比較兩個Matrix的數值是否相同。

2.hashCode

獲取Matrix的雜湊值。

3.toString

將Matrix轉換為字串: Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]}

4.toShortString

將Matrix轉換為短字串: [1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]

數值操作

數值操作這一組方法可以幫助我們直接控制Matrix裡面的數值。

1.set

void set (Matrix src)

沒有返回值,有一個引數,作用是將引數Matrix的數值複製到當前Matrix中。如果引數為空,則重置當前Matrix,相當於reset()

2.reset

void reset ()

重置當前Matrix(將當前Matrix重置為單位矩陣)。

3.setValues

void setValues (float[] values)

setValues的引數是浮點型的一維陣列,長度需要大於9,拷貝陣列中的前9位數值賦值給當前Matrix。

4.getValues

void getValues (float[] values)

很顯然,getValues和setValues是一對方法,引數也是浮點型的一維陣列,長度需要大於9,將Matrix中的數值拷貝進引數的前9位中。

數值計算

1.mapPoints

void mapPoints (float[] pts)

void mapPoints (float[] dst, float[] src)

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

計算一組點基於當前Matrix變換後的位置,(由於是計算點,所以引數中的float陣列長度一般都是偶數的,若為奇數,則最後一個數值不參與計算)。

它有三個過載方法:

(1) void mapPoints (float[] pts) 方法僅有一個引數,pts陣列作為引數傳遞原始數值,計算結果仍存放在pts中。

示例:

// 初始資料為三個點 (0, 0) (80, 100) (400, 300) 
float[] pts = new float[]{0, 0, 80, 100, 400, 300};

// 構造一個matrix,x座標縮放0.5
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f);

// 輸出pts計算之前資料
Log.i(TAG, "before: "+ Arrays.toString(pts));

// 呼叫map方法計算
matrix.mapPoints(pts);

// 輸出pts計算之後資料
Log.i(TAG, "after : "+ Arrays.toString(pts));

結果:

before: [0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
after : [0.0, 0.0, 40.0, 100.0, 200.0, 300.0]

(2) void mapPoints (float[] dst, float[] src) ,src作為引數傳遞原始數值,計算結果存放在dst中,src不變。

如果原始資料需要保留則一般使用這種方法。

示例:

// 初始資料為三個點 (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(TAG, "before: src="+ Arrays.toString(src));
Log.i(TAG, "before: dst="+ Arrays.toString(dst));

// 呼叫map方法計算
matrix.mapPoints(dst,src);

// 輸出計算之後資料
Log.i(TAG, "after : src="+ Arrays.toString(src));
Log.i(TAG, "after : dst="+ Arrays.toString(dst));

結果:

before: src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
before: dst=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
after : src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
after : dst=[0.0, 0.0, 40.0, 100.0, 200.0, 300.0]

(3) void mapPoints (float[] dst, int dstIndex,float[] src, int srcIndex, int pointCount) 可以指定只計算一部分數值。

引數 摘要
dst 目標資料
dstIndex 目標資料儲存位置起始下標
src 源資料
srcIndex 源資料儲存位置起始下標
pointCount 計算的點個數

示例:

將第二、三個點計算後儲存進dst最開始位置。

// 初始資料為三個點 (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(TAG, "before: src="+ Arrays.toString(src));
Log.i(TAG, "before: dst="+ Arrays.toString(dst));

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

// 輸出計算之後資料
Log.i(TAG, "after : src="+ Arrays.toString(src));
Log.i(TAG, "after : dst="+ Arrays.toString(dst));

結果:

before: src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
before: dst=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
after : src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
after : dst=[40.0, 100.0, 200.0, 300.0, 0.0, 0.0]

2.mapRadius

float mapRadius (float radius)

測量半徑,由於圓可能會因為畫布變換變成橢圓,所以此處測量的是平均半徑。

示例:

float radius = 100;
float result = 0;

// 構造一個matrix,x座標縮放0.5
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f);

Log.i(TAG, "mapRadius: "+radius);

result = matrix.mapRadius(radius);

Log.i(TAG, "mapRadius: "+result);

結果:

mapRadius: 100.0
mapRadius: 70.71068

3.mapRect

boolean mapRect (RectF rect)

boolean mapRect (RectF dst, RectF src)

測量矩形變換後位置。

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

示例:

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

// 構造一個matrix
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f);
matrix.postSkew(1,0);

Log.i(TAG, "mapRadius: "+rect.toString());

boolean result = matrix.mapRect(rect);

Log.i(TAG, "mapRadius: "+rect.toString());
Log.e(TAG, "isRect: "+ result);

結果:

mapRadius: RectF(400.0, 400.0, 1000.0, 800.0)
mapRadius: RectF(600.0, 400.0, 1300.0, 800.0)
isRect: false

由於使用了錯切,所以返回結果為false。

(2) boolean mapRect (RectF dst, RectF src) 測量src並將測量結果放入dst中,返回值是判斷矩形經過變換後是否仍為矩形,和之前沒有什麼太大區別,此處就不囉嗦了。

4.mapVectors

測量向量。

void mapVectors (float[] vecs)

void mapVectors (float[] dst, float[] src)

void mapVectors (float[] dst, int dstIndex, float[] src, int srcIndex, int vectorCount)

mapVectorsmapPoints 基本上是相同的,可以直接參照上面的mapPoints使用方法。

而兩者唯一的區別就是mapVectors不會受到位移的影響,這符合向量的定律,如果你不瞭解的話,請找到以前教過你的老師然後把學費要回來。

區別:

float[] src = new float[]{1000, 800};
float[] dst = new float[2];

// 構造一個matrix
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 1f);
matrix.postTranslate(100,100);

// 計算向量, 不受位移影響
matrix.mapVectors(dst, src);
Log.i(TAG, "mapVectors: "+Arrays.toString(dst));

// 計算點
matrix.mapPoints(dst, src);
Log.i(TAG, "mapPoints: "+Arrays.toString(dst));

結果:

mapVectors: [500.0, 800.0]
mapPoints: [600.0, 900.0]

set、pre 與 post

對於四種基本變換 平移(translate)、縮放(scale)、旋轉(rotate)、 錯切(skew) 它們每一種都三種操作方法,分別為 設定(set)、 前乘(pre) 和 後乘 (post)。而它們的基礎是Concat,通過先構造出特殊矩陣然後用原始矩陣Concat特殊矩陣,達到變換的結果。

關於四種基本變換的知識和三種對應操作的區別,詳細可以參考 Canvas之畫布操作Matrix原理 這兩篇文章的內容。

由於之前的文章已經詳細的講解過了它們的原理與用法,所以此處就簡要的介紹一下:

方法 簡介
set 設定,會覆蓋掉之前的數值,導致之前的操作失效。
pre 前乘,相當於矩陣的右乘, M' = M * S (S指為特殊矩陣)
post 後乘,相當於矩陣的左乘,M' = S * M (S指為特殊矩陣)

Matrix 相關的重要知識:

  • 1.一開始從Canvas中獲取到到Matrix並不是初始矩陣,而是經過偏移後到矩陣,且偏移距離就是距離螢幕左上角的位置。

  • 這個可以用於判定View在螢幕上的絕對位置,View可以根據所處位置做出調整。

  • 2.構造Matrix時使用的是矩陣乘法,前乘(pre)與後乘(post)結果差別很大。

  • 這個直接參見上一篇文章 Matrix原理 即可。

  • 3.受矩陣乘法影響,後面的執行的操作可能會影響到之前的操作。

  • 使用時需要注意構造順序。

特殊方法

這一類方法看似不起眼,但拿來稍微加工一下就可能製作意想不到的效果。

1.setPolyToPoly

boolean setPolyToPoly (
        float[] src,    // 原始陣列 src [x,y],儲存內容為一組點
        int srcIndex,   // 原始陣列開始位置
        float[] dst,    // 目標陣列 dst [x,y],儲存內容為一組點
        int dstIndex,   // 目標陣列開始位置
        int pointCount) // 測控點的數量 取值範圍是: 0到4

Poly全稱是Polygon,多邊形的意思,瞭解了意思大致就能知道這個方法是做什麼用的了,應該與PS中自由變換中的扭曲有點類似。

從引數我們可以瞭解到setPolyToPoly最多可以支援4個點,這四個點通常為圖形的四個角,可以通過這四個角將檢視從矩形變換成其他形狀。

簡單示例:

public class MatrixSetPolyToPolyTest extends View {

    private Bitmap mBitmap;             // 要繪製的圖片
    private Matrix mPolyMatrix;         // 測試setPolyToPoly用的Matrix

    public MatrixSetPolyToPolyTest(Context context) {
        super(context);

        initBitmapAndMatrix();
    }

    private void initBitmapAndMatrix() {
        mBitmap = BitmapFactory.decodeResource(getResources(),
                R.drawable.poly_test);

        mPolyMatrix = new Matrix();


        float[] src = {0, 0,                                    // 左上
                mBitmap.getWidth(), 0,                          // 右上
                mBitmap.getWidth(), mBitmap.getHeight(),        // 右下
                0, mBitmap.getHeight()};                        // 左下

        float[] dst = {0, 0,                                    // 左上
                mBitmap.getWidth(), 400,                        // 右上
                mBitmap.getWidth(), mBitmap.getHeight() - 200,  // 右下
                0, mBitmap.getHeight()};                        // 左下

        // 核心要點
        mPolyMatrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1); // src.length >> 1 為位移運算 相當於處以2

        // 此處為了更好的顯示對圖片進行了等比縮放和平移(圖片本身有點大)
        mPolyMatrix.postScale(0.26f, 0.26f);
        mPolyMatrix.postTranslate(0,200);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 根據Matrix繪製一個變換後的圖片
        canvas.drawBitmap(mBitmap, mPolyMatrix, null);
    }
}

文章發出後有小夥伴在GitHub上提出疑問,說此處講解到並不清楚,尤其是最後的一個引數,所以特此補充一下內容。

我們知道pointCount支援點的個數為0到4個,四個一般指圖形的四個角,屬於最常用的一種情形,但前面幾種是什麼情況呢?

釋出此文的時候之所以沒有講解0到3的情況,是因為前面的幾種情況在實際開發中很少會出現, 才不是因為偷懶呢,哼。

pointCount 摘要
0 相當於reset
1 相當於translate
2 可以進行 縮放、旋轉、平移 變換
3 可以進行 縮放、旋轉、平移、錯切 變換
4 可以進行 縮放、旋轉、平移、錯切以及任何形變

從上表我們可以觀察出一個規律, 隨著pointCount數值增大setPolyToPoly的可以操作性也越來越強,這不是廢話麼,可調整點數多了能幹的事情自然也多了。

只列一個表格就算交代完畢了顯得誠意不足,為了彰顯誠意,接下來詳細的講解一下。

為什麼說前面幾種情況在實際開發中很少出現?

作為開發人員,寫出來的程式碼出了要讓機器”看懂”,沒有歧義之外,最重要的還是讓人看懂,以方便後期的維護修改,從上邊的表格中可以看出,前面的幾種種情況都可以有更直觀的替代方法,只有四個引數的情況下的特殊形變是沒有替代方法的。

測控點選取位置?

測控點可以選擇任何你認為方便的位置,只要src與dst一一對應即可。不過為了方便,通常會選擇一些特殊的點: 圖形的四個角,邊線的中心點以及圖形的中心點等。不過有一點需要注意,測控點選取都應當是不重複的(src與dst均是如此),如果選取了重複的點會直接導致測量失效,這也意味著,你不允許將一個方形(四個點)對映為三角形(四個點,但其中兩個位置重疊),但可以接近於三角形。

作用範圍?

作用範圍當然是設定了Matrix的全部區域,如果你將這個Matrix賦值給了Canvas,它的作用範圍就是整個畫布,如果你賦值給了Bitmap,它的作用範圍就是整張圖片。


接下來用示例演示一下,所有示例的src均為圖片大小,dst根據手勢變化。

pointCount為0

pointCount為0和reset是等價的,而不是保持matrix不變,在最底層的實現中可以看到這樣的程式碼:

if (0 == count) {
    this->reset();
    return true;
}

pointCount為1

pointCount為0和translate是等價的,在最底層的實現中可以看到這樣的程式碼:

if (1 == count) {
    this->setTranslate(dst[0].fX - src[0].fX, dst[0].fY - src[0].fY);
    return true;
}

平移的距離是dst - src.

當測控點為1的時候,由於你只有一個點可以控制,所以你只能拖拽著它在2D平面上滑動。

pointCount為2

當pointCount為2的時候,可以做縮放、平移和旋轉。

pointCount為3

當pointCount為3的時候,可以做縮放、平移、旋轉和錯切。

pointCount為4

當pointCount為4的時候,你可以將影象拉伸為任意四邊形。

相關推薦

定義View-Matrix

這應該是目前最詳細的一篇講解Matrix的中文文章了,在上一篇文章Matrix原理中,我們對Matrix做了一個簡單的瞭解,偏向理論,在本文中則會詳細的講解Matrix的具體用法,以及與Matrix相關的一些實用技巧。 ⚠️ 警告:測試本文章示例之前請關閉硬體加速。

定義View-MotionEvent

Android MotionEvent 詳解,之前用了兩篇文章 事件分發機制原理 和 事件分發機制詳解 來講解事件分發,而作為事件分發主角之一的 MotionEvent 並沒有過多的說明,本文就帶大家瞭解 MotionEvent 的相關內容,簡要介紹觸控事件,主要包括 單點觸控、多點

定義View-Matrix Camera

本篇依舊屬於Matrix,主要講解Camera,Android下有很多相機應用,其中的美顏相機更是不少,不過今天這個Camera可不是我們平時拍照的那個相機,而是graphic包下的Camera,專業給View拍照的相機,不過既然是相機,作用都是類似的,主要是將3D的內容拍扁變成2D

定義View-Matrix原理

本文內容偏向理論,和 畫布操作 有重疊的部分,本文會讓你更加深入的瞭解其中的原理。 本篇的主角Matrix,是一個一直在後臺默默工作的勞動模範,雖然我們所有看到View背後都有著Matrix的功勞,但我們卻很少見到它,本篇我們就看看它是何方神聖吧。 由於Goog

定義View-手勢檢測(GestureDecetor)

Android 手勢檢測,主要是 GestureDetector 相關內容的用法和注意事項,本文依舊屬於事件處理這一體系,部分內容會涉及到之前文章提及過的知識點,如果你沒看過之前的文章,可以到 自定義 View 系列 來檢視這些內容。 在開發 Android 手機應用過程中,可

定義View-多點觸控

Android 多點觸控詳解,在前面的幾篇文章中我們大致瞭解了 Android 中的事件處理流程和一些簡單的處理方案,本次帶大家瞭解 Android 多點觸控相關的一些知識。 多點觸控 ( Multitouch,也稱 Multi-touch ),即同時接受螢幕上多個點的人機互動

定義View-特殊控制元件的事件處理方案

本文帶大家瞭解 Android 特殊形狀控制元件的事件處理方式,主要是利用了 Region 和 Matrix 的一些方法,超級實用的事件處理方案,相信看完本篇之後,任何奇葩控制元件的事件處理都會變得十分簡單。 不得不說,Android 對事件體系封裝的非常棒,即便對事件體系不太

定義View-事件分發機制

Android 事件分發機制詳解,在上一篇文章 事件分發機制原理 中簡要分析了一下事件分發機制的原理,原理是十分簡單的,一句話就能總結:責任鏈模式,事件層層傳遞,直到被消費。 雖然原理簡單,但是隨著 Android 不斷的發展,實際運用場景也越來越複雜,所以想要徹底玩轉事件分發機制還

定義View-PathMeasure

可以看到,在經過 Path之基本操作 Path之貝塞爾曲線 和 Path之完結篇 後, Path中各類方法基本上都講完了,表格中還沒有講解到到方法就是矩陣變換了,難道本篇終於要講矩陣了? 非也,矩陣這一部分仍在後面單獨講解,本篇主要講解 PathMeasure 這個類與 Path 的

定義View-Path之貝塞爾曲線

在上一篇文章Path之基本操作中我們瞭解了Path的基本使用方法,本次瞭解Path中非常非常非常重要的內容-貝塞爾曲線。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除了在API21(即安卓版本5.0)以上

定義View-Path之基本操作

在上一篇Canvas之圖片文字中我們瞭解瞭如何使用Canvas中繪製圖片文字,結合前幾篇文章,Canvas的基本操作已經差不多完結了,然而Canvas不僅僅具有這些基本的操作,還可以更加炫酷,本次會了解到path(路徑)這個Canvas中的神器,有了這個神器,就能創造出更多炫(zhu

定義View-Canvas之圖片文字

在上一篇文章Canvas之畫布操作中我們瞭解了畫布的一些基本操作方法,本次瞭解一些繪製圖片文字相關的內容。如果你對前幾篇文章講述的內容熟練掌握的話,那麼恭喜你,本篇結束之後,大部分的自定義View已經難不倒你了,當然了,這並不是終點,接下來還會有更加炫酷的技能。 一.Canva

定義View-分類與流程

本章節為什麼要叫進階篇?(雖然講的是基礎內容),因為從本篇開始,將會逐漸揭開自定義View的神祕面紗,每一篇都將比上一篇內容更加深入,利用所學的知識能夠製作更加炫酷自定義View,就像在臺階上一樣,每一篇都更上一層,幫助大家一步步走向人生巔峰,出任CEO,迎娶白富美。 誤

定義View-Path之完結篇

經歷過前兩篇 Path之基本操作 和 Path之貝塞爾曲線 的講解,本篇終於進入Path的收尾篇,本篇結束後Path的大部分相關方法都已經講解完了,但Path還有一些更有意思的玩法,應該會在後續的文章中出現。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除

定義View-縮放手勢檢測(ScaleGestureDecetor)

0. 前言 Android 縮放手勢檢測,ScaleGestureDetector 相關內容的用法和注意事項,本文依舊屬於事件處理這一體系,在大多數的情況下,縮放手勢都不是單獨存在的,需要配合其它的手勢來使用,所以推薦配合 手勢檢測(GestureDetector) 一

定義View-Canvas之畫布操作

Canvas之畫布操作 上一篇Canvas之繪製基本形狀中我們瞭解瞭如何使用Canvas繪製基本圖形,本次瞭解一些基本的畫布操作。 本來想把畫布操作放到後面部分的,但是發現很多圖形繪製都離不開畫布操作,於是先講解一下畫布的基本操作方法。

定義 View :貝塞爾曲線

在上一篇文章Path之基本圖形中我們瞭解了Path的基本使用方法,本次瞭解Path中非常非常非常重要的內容-貝塞爾曲線。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除了在API21(即安卓版本5.0)以上才新增的方法。忍不住吐槽一下,為啥看起來有些順手就能寫的

定義View:Path基本操作

在上一篇Canvas之圖片文字中我們瞭解瞭如何使用Canvas中繪製圖片文字,結合前幾篇文章,Canvas的基本操作已經差不多完結了,然而Canvas不僅僅具有這些基本的操作,還可以更加炫酷,本次會了解到path(路徑)這個Canvas中的神器,有了這個神器,就能創造出更多炫(

定義View: 圖片文字

在上一篇文章Canvas之畫布操作中我們瞭解了畫布的一些基本操作方法,本次瞭解一些繪製圖片文字相關的內容。如果你對前幾篇文章講述的內容熟練掌握的話,那麼恭喜你,本篇結束之後,大部分的自定義View已經難不倒你了,當然了,這並不是終點,接下來還會有更加炫酷的技能。 一.Canva

定義 View :Path 完結篇(偽)

經歷過前兩篇 Path之基本操作 和 Path之貝塞爾曲線 的講解,本篇終於進入Path的收尾篇,本篇結束後Path的大部分相關方法都已經講解完了,但Path還有一些更有意思的玩法,應該會在後續的文章中出現吧,嗯,應該會的ˊ_>ˋ 一.Path常用方法表 為了相容性