我們經常在項目中使用
ImageView的scaleType來設置顯示圖片的顯示方式,其中有一種type是矩陣變化matrix方式我們可能在項目中會相對少用,具體matrix的使用方式,不是文章的重點,可以自行google或者參考一下其他同學寫的文章,主要我們講一下Matrix.setScale()方式在使用過程中踩的坑,同樣可以引申到Matrix的其他方法
參考文章
基本方法介紹
接下來我們簡單介紹一下Matrix類常用的幾種變化:
-
setTranslate(float dx,float dy):控制Matrix進行平移; -
setSkew(float kx,float ky,float px,float py):控制Matrix以px,py為軸心進行傾斜,kx,ky為X,Y方向上的傾斜距離; -
setRotate(float degress):控制Matrix進行旋轉,degress控制旋轉的角度; -
setRorate(float degress,float px,float py):設置以px,py為軸心進行旋轉,degress控制旋轉角度; -
setScale(float sx,float sy):設置Matrix進行縮放,sx,sy控制X,Y方向上的縮放比例; -
setScale(float sx,float sy,float px,float py):設置Matrix以px,py為軸心進行縮放(此處有坑),sx,sy控制X,Y方向上的縮放比例;
當然這里只是set方法,還有相應的preXXX()和postXXX()方法,其中的區(qū)別就是pre和post和執(zhí)行的順序有關系,set開頭的方法是直接清除之前Matrix內的所有變化,重新設置一次。
- set是直接設置Matrix的值,每調用一次,之前
Matrix內的所有變化都重置,整個Matrix的數組都會變掉 - post是后乘,當前的矩陣乘以參數給出的矩陣。可以連續(xù)多次使用post,來完成所需的整個變換
- pre是前乘,參數給出的矩陣乘以當前的矩陣。所以操作是在當前矩陣的最前面發(fā)生的。
我們知道m(xù)atrix實際上是一個3x3的矩陣,根據線性代數里面的知識,矩陣相乘,前乘和后乘得到的結果是不一樣的,所以我們做的矩陣任何的變化(平移、縮放、旋轉等)本質上都是對matrix矩陣進行操作,理解了這一點,對我們后面的踩坑有莫大的幫助
踩坑
這里講一下在上面setScale(float sx,float sy,float px,float py)在實際應用中的坑,我們先看下Android注釋是怎么解釋這個方法的
Set the matrix to scale by sx and sy, with a pivot point at (px, py).The pivot point is the coordinate that should remain unchanged by the specified transformation.
這里什么意思呢?就是設置matrix根據sx和sy的值進行縮放,那么px和py是什么鬼呢?網絡上很多解釋是解釋成中心點,在這個中心點的基礎上進行縮放,其實不然,后面我們會講到這里為什么會有坑,如果你把這兩個參數當成縮放的中心點來看的話,在實際測試過程中你會發(fā)現圖片并不是在當前中心點縮放,在縮放的同時,位置還會進行偏移(這里行為跟postScale(float sx,float sy,float px,float py)中的px與py不一致,post行為是真的以此處為中心點進行縮放)。這里為什么會這樣呢?坑了我好久,不死心,覺得一定有什么貓膩,所以最好的方式,就是看看他的源碼,看下到底是什么原因導致的
先看下Matrix.java類
/**
* Set the matrix to scale by sx and sy, with a pivot point at (px, py).
* The pivot point is the coordinate that should remain unchanged by the
* specified transformation.
*/
public void setScale(float sx, float sy, float px, float py) {
native_setScale(native_instance, sx, sy, px, py);
}
納尼?調用了native_setScale方法,可能很多人看到這一步需要去看native層的方法就放棄,但是沒有什么能難倒攻城獅,不急,我們接著往下看
private static native void native_setScale(int native_object,
float sx, float sy, float px, float py);
根據一些jni的知識,我們知道在native層的代碼肯定會有一個native_setScale的方法與之對應,所以我們需要去找到該native層的實現方法,看看他的代碼。那么接下來我們去哪里找呢?可能很多同學會想到去下源碼,可是源碼何其多,網絡情況良好的情況下個幾天都是正常的,這里我推薦一個網站androidxref,這里面有你所需要的各個版本的源碼,并且可以根據查詢條件快速查找,查找也是非常快的。最后我們在Matrix.cpp中找到了
{"native_setScale","(IFFFF)V", (void*) SkMatrixGlue::setScale__FFFF},
// 對應的方法
static void setScale__FFFF(JNIEnv* env, jobject clazz, SkMatrix* obj, jfloat sx, jfloat sy, jfloat px, jfloat py) {
SkScalar sx_ = SkFloatToScalar(sx);
SkScalar sy_ = SkFloatToScalar(sy);
SkScalar px_ = SkFloatToScalar(px);
SkScalar py_ = SkFloatToScalar(py);
obj->setScale(sx_, sy_, px_, py_);
}
最后我們看一下obj->setScale(sx_, sy_, px_, py_);的實現
void SkMatrix::setScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) {
if (SK_Scalar1 == sx && SK_Scalar1 == sy) {
this->reset();
} else {
fMat[kMScaleX] = sx;
fMat[kMScaleY] = sy;
fMat[kMTransX] = px - SkScalarMul(sx, px);
fMat[kMTransY] = py - SkScalarMul(sy, py);
fMat[kMPersp2] = kMatrix22Elem;
fMat[kMSkewX] = fMat[kMSkewY] =
fMat[kMPersp0] = fMat[kMPersp1] = 0;
this->setTypeMask(kScale_Mask | kTranslate_Mask | kRectStaysRect_Mask);
}
}
這里我們看到實際上不當當進行了縮放操作,還進行了平移操作,所以我們實際進行縮放的時候,還伴隨著平移操作,實際上這里最后換算成的算法是fMat[kMTransX] = px - sx * px;。所以想要保持在縮放過程中不進行平移,那么我們需要改造一下算法px = fMat[kMTransX]/(1-sx),那么有人會問,那我怎么能提前知道fMat[kMTransX]的值呢,這里就要在每次縮放操作前,根據當前需要縮放的比例,預測量出值,這里就不介紹怎么預測量,相信機智的小伙伴肯定已經有想法了~好了,這里就講到這里,有問題請留言,我會及時回復,歡迎交流~