目錄(transform)
- 基礎(chǔ)及矩陣概念
- 2D仿射
- 3D仿射
1. 基礎(chǔ)及矩陣概念

- 在iOS的動畫效果中,變換是很常見的,包括仿射變換和3D變換等。變換的終極原理就是矩陣的乘法運算,3D變換涉及到三維透視投影的一些原理知識。
-
transform基本概念
transform在矩陣變換的層面上改變視圖的顯示效果,完成旋轉(zhuǎn)、形變、平移等等操作。在它被修改的同時,視圖的frame也會被真實改變。有兩個數(shù)據(jù)類型用來表示transform,分別是CGAffineTransform和CATransform3D。前者作用于UIView,后者為layer層次的變換類型?;诤笳呖梢詫崿F(xiàn)更加強大的功能。
UIView可以通過設(shè)置transform屬性做變換,但實際上它只是封裝了內(nèi)部圖層的變換。
CALayer同樣也有一個transform屬性,但它的類型是CATransform3D,而不是CGAffineTransform。CALayer對應(yīng)于UIView的transform屬性叫做affineTransform, - 矩陣概念
矩陣 × 坐標(biāo):當(dāng)你用矩陣乘以一個坐標(biāo)時,它們的乘積就是一個變換后的新坐標(biāo)。
齊次坐標(biāo):為了把二維圖形的變化統(tǒng)一在一個坐標(biāo)系里,引入了齊次坐標(biāo)的概念,即把一個圖形用一個三維矩陣表示,其中第三列總是(0,0,1)(為了能讓矩陣做乘法),用來作為坐標(biāo)系的標(biāo)準(zhǔn)。所以所有的變化都由前兩列完成。
為了能讓矩陣做乘法:左邊矩陣的列數(shù)一定要和右邊矩陣的行數(shù)個數(shù)相同,所以要給矩陣填充一些標(biāo)志值,使得既可以讓矩陣做乘法,又不改變運算結(jié)果,并且沒必要存儲這些添加的值,因為它們的值不會發(fā)生變化,但是要用來做運算。因此,通常會用3×3(而不是2×3)的矩陣來做二維變換.
當(dāng)對圖層應(yīng)用變換矩陣,圖層矩形內(nèi)的每一個點都被相應(yīng)地做變換,從而形成一個新的四邊形的形狀。CGAffineTransform中的“仿射”的意思是無論變換矩陣用什么值,圖層中平行的兩條線在變換之后任然保持平行,CGAffineTransform可以做出任意符合上述標(biāo)注的變換
單位矩陣
CGAffineTransformIdentity
-
仿射變換的原理和計算
仿射變化原理是數(shù)學(xué)中的矩陣原理(線性代數(shù)-矩陣分析),要弄明白仿射矩陣對作用點的影響,還得先看看矩陣的乘法怎么計算。基礎(chǔ)-矩陣的乘法
計算規(guī)則:- 不符合交換律(A和B是矩陣,AB不一定等于BA)
- 當(dāng)矩陣A的列數(shù)等于矩陣B的行數(shù)是,才可以計算
- 計算的結(jié)果矩陣C的行數(shù)等于A的行數(shù),列數(shù)等于B的列數(shù)(如A是m×n矩陣和B是n×p矩陣,它們的乘積C是一個m×p矩陣 )
- 結(jié)果矩陣C的第 i 行第 j 列的元素Cij 等于矩陣A的第 i 行的元素與矩陣B的第 j 列對應(yīng)元素乘積之和
矩陣A =
[1 1]
[2 0]
矩陣B =
[0 2 3]
[1 1 2]
計算過程:
矩陣C = A * B =
[(1 * 0 + 1 * 1) (1 * 2 + 1 * 1) (1 * 3 + 1 * 2)]
[(2 * 0 + 0 * 1) (2 * 2 + 0 * 1) (2 * 3 + 0 * 2)]
矩陣C =
[1 3 5]
[0 4 6]
- 仿射變換的矩陣計算
仿射計算中,(以二維坐標(biāo)為例,坐標(biāo)點為x,y)我們設(shè)我們的坐標(biāo)點矩陣為
坐標(biāo)點矩陣
A = [x y 1]
仿射變換基礎(chǔ)矩陣為:
B =
[a b 0]
[c d 0]
[tx ty 1]
根據(jù)矩陣計算規(guī)則我們知道A x B的結(jié)果是一個1行3列的矩陣,設(shè)A x B得到的新矩陣C ,那么C的矩陣應(yīng)該為
C = [(a*x+c*y+tx) (b*x+d*y+ty) 1]
設(shè)C為 = [x' y' 1] , 那么可以得到
x' = a*x + c*y + tx
y' = b*x + d*y + ty
2. 2D仿射
- 仿射是什么
仿射變換可以理解為- 對坐標(biāo)進行放縮,旋轉(zhuǎn),平移后取得新坐標(biāo)的值。
- 經(jīng)過對坐標(biāo)軸的放縮,旋轉(zhuǎn),平移后原坐標(biāo)在在新坐標(biāo)領(lǐng)域中的值。
- transform是一個矩陣,變換后的點坐標(biāo)等于之前的點坐標(biāo)乘以矩陣
當(dāng)操縱一個變換的時候,初始生成一個什么都不做的變換很重要--也就是創(chuàng)建一個CGAffineTransform類型的空值,矩陣論中稱作單位矩陣,Core Graphics同樣也提供了一個方便的常量:CGAffineTransformIdentity
iOS封裝了幾個好用的CGAffineTrans的API去實現(xiàn)仿射變換的效果
注意:帶Make的,起點固定,每次控制的事件只針對起點。不帶Make的:為一個變換再加上平移,針對上一個位置,不針對起點。
//位移仿射
CGAffineTransformMakeTranslation
CGAffineTransformTranslate
//旋轉(zhuǎn)仿射
CGAffineTransformMakeRotation
CGAffineTransformRotate
//縮放仿射
CGAffineTransformMakeScale
CGAffineTransformScale
//疊加仿射效果
CGAffineTransformConcat
//仿射矩陣方法,可以直接做效果疊加
CGAffineTransformMake (sx,shx,shy,sy,tx,ty)
帶make的方法是直接返回一個仿射變換效果,不帶make方法是用來給已有的仿射效果疊加效果
類似于CGAffineTransformConcat方法的作用
CATransform3D也對應(yīng)一組相似的api,這個后面在研究到它的時候仔細(xì)說說.
在大多數(shù)情況下用這幾個封裝好的仿射方法基本就能實現(xiàn)大多數(shù)的需求。但是既然是研究仿射,可以看CGAffineTransform最基礎(chǔ)的那個仿射方法的使用,能理解這個方法的使用,基本上就知道仿射是個什么意思了。
CGAffineTransformMake(CGFloat a,CGFloat b,CGFloat c,CGFloat d,CGFloat tx,CGFloat ty)
這個方法的6個參數(shù)可以拼出一個矩陣
//仿射矩陣
[a b 0]
[c d 0]
[tx ty 1]
仿射變化的定義有點復(fù)雜,我自己理解就是: 點p(以二維坐標(biāo)為例)通過仿射矩陣C 后變成新的點p' 。
以上面的縮放仿射變化CGAffineTransformScale為例。就是view中每一個點p通過矩陣C后,view中的每一個新的點p'組成的新的圖形,相比之前的view有了縮放的效果。
這步很關(guān)鍵。根據(jù)這個公式,那么仿射矩陣就可以分成5種分別對應(yīng):平移(Translation),縮放(Scale),翻轉(zhuǎn)(Flip),旋轉(zhuǎn)(Rotation),剪切(Shear)
平移演化
設(shè)a,d=1 c,b = 0 那么
x' = a*x + c*y + tx
y' = b*x + d*y + ty
就變成了
x' = x + tx
y' = y + ty
這個不就是新的點P'(x' y') 是根據(jù)原來的P在x和y分別添加了一個常量就可以得到了嘛? 所以這個就是仿射中的平移效果了。把a,b,c,d帶入仿射的基礎(chǔ)矩陣,就得了仿射的位移矩陣
//仿射基礎(chǔ)矩陣
[a b 0]
[c d 0]
[tx ty 1]
//仿射位移矩陣
[1 0 0]
[0 1 0]
[tx ty 1]
再來看看代碼
向右移動300的仿射效果
CGAffineTransform translate = CGAffineTransformMakeTranslation(300, 0)
使用仿射基礎(chǔ)方法
CGAffineTransform translate = CGAffineTransformMake(1,0,0,1,300,0)
縮放演化
x' = a*x + c*y + tx
y' = b*x + d*y + ty
設(shè) c,b,tx,ty = 0 ,得到
x' = a*x
y' = d*y
新坐標(biāo)(x',y')和原坐標(biāo)是倍數(shù)關(guān)系,這個就可以用來做縮放效果了。所以得到縮放矩陣
//仿射縮放矩陣
[a 0 0]
[0 d 0]
[0 0 1]
//也可以寫成
[sx 0 0]
[0 sy 0]
[0 0 1]
代碼
//x和y都放大1倍
CGAffineTransform scaleAffine = CGAffineTransformMakeScale(2, 2)
//使用仿射基礎(chǔ)方法
CGAffineTransform scaleAffine = CGAffineTransformMake(2,0,0,2,0,0)
剪切演化
x' = a*x + c*y + tx
y' = b*x + d*y + ty
設(shè) a,d = 1 tx,ty = 0 ,得到
x' = x + cy
y' = y + bx
x和y的變化總是相互關(guān)聯(lián),這個就是剪切(Shear)
//仿射剪切矩陣
[1 b 0]
[c 1 0]
[0 0 1]
//也可以寫成
[1 shx 0]
[shy 1 0]
[0 0 1]
代碼
//使用仿射基礎(chǔ)方法CGAffineTransformMake,設(shè)置x和y都為0.5的斜切
CGAffineTransform shearAffine = CGAffineTransformMake(1,0.5,0.5,1,0,0)
剪切效果只能用CGAffineTransformMake實現(xiàn),但是通過CATransform3DMakeRotation可以有類似的效果
旋轉(zhuǎn)演化
//仿射旋轉(zhuǎn)矩陣
[cosa sina 0]
[-sina cosa 0]
[0 0 1]
代碼
CGAffineTransform rotation = CGAffineTransformMake(CGFloat( cos(M_PI_4) ), CGFloat( sin(M_PI_4) ), -CGFloat( sin(M_PI_4) ), CGFloat( cos(M_PI_4) ), 0, 0)
翻轉(zhuǎn)演化
我不知道使用CGAffineTransformMake()如何設(shè)置矩陣可以實現(xiàn)翻轉(zhuǎn)效果。我閱讀的所有Flip效果都是使用CATransform3D實現(xiàn)的,代碼如下
//Flip仿射,要是有3D去實現(xiàn)
CATransform3D flip = CATransform3DMakeRotation(angle, x, y, z)
//示例
CATransform3D flipX = CATransform3DMakeRotation(CGFloat(M_PI), 1, 0, 0)
CATransform3D flipY = CATransform3DMakeRotation(CGFloat(M_PI), 0, 1, 0)
CATransform3D flipZ = CATransform3DMakeRotation(CGFloat(M_PI), 0, 0, 1)
總結(jié)一下CATransform3DMakeRotation方法的6個參數(shù)
在不考慮旋轉(zhuǎn)時,CATransform3DMakeRotation6個參數(shù)可以寫成
//sx,sy:縮放因子
//shx,shy:斜切因子
//tx,ty:移動因子
CGAffineTransformMake (sx,shx,shy,sy,tx,ty)
這個是一個初始化矩陣,帶入矩陣算法計算后的結(jié)構(gòu)會得到
x'=x , y'=y
它的作用是清除之前對矩陣設(shè)置的仿射效果,或者用來初始化一個原始無效果的仿射矩陣
矩陣初始值。[ 1 0 0 1 0 0 ]
[ 1 0 0 ]
[ 0 1 0 ]
[ 0 0 1 ]
方法:
//用來連接兩個變換效果并返回。返回的t = t1 * t2
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2)
//矩陣初始值。[ 1 0 0 1 0 0 ]
CGAffineTransformIdentity
//檢查是否有做過仿射效果
CGAffineTransformIsIdentity(transform)
//檢查2個仿射效果是否相同
CGAffineTransformEqualToTransform(transform1,transform2)
//仿射效果反轉(zhuǎn)(反效果,比如原來擴大,就變成縮小)
CGAffineTransformInvert(transform)
- 應(yīng)用
放射矩陣一個常用的情形就是根據(jù)用戶的手勢來相應(yīng)的改變視圖的變換
UIPanGestureRecognizer對應(yīng)位移
UIPinchGestureRecognizer對應(yīng)縮放
UIRotationGestureRecognizer對應(yīng)旋轉(zhuǎn)
通常如果需要看到實時的手指移動視圖就相應(yīng)的變換的技巧就是,每次接收到對應(yīng)的gesture時間就相應(yīng)的改變view的transform,然后吧這個gesture對應(yīng)的translation、scale、rotation置為初始值。
3. 3D仿射
- 三維坐標(biāo)系:視角垂直與屏幕而言,x軸向右,y軸向下,z軸垂直屏幕向外。和
UIView嚴(yán)格的二維坐標(biāo)系不同,CALayer存在于一個三維空間當(dāng)中。CALayer還有另外兩個屬性,zPosition和anchorPoint
最實用的功能就是改變圖層的顯示順序。
self.greenView.layer.zPosition=1.0f;
- 錨點 anchorPoint
圖層的錨點anchorPoint:是一個CGPoint值,x,y取值范圍(0~1),默認(rèn)為(0.5,0.5) 對于圖層本身而言,顧名思義,錨點就用來定位圖層的點。錨點有兩個職能:
(1)與position一同確定圖層相對于父圖層的位置;
(2)通過設(shè)置該屬性來實現(xiàn)視圖圍繞不同位置旋轉(zhuǎn)。記得在視圖添加到父視圖之后在進行設(shè)置,因為frame也是相對于錨點的。
作為圖層旋轉(zhuǎn)、平移、縮放的中心
self.menuViewController.view.layer.anchorPoint = CGPointMake(1.0,0);
當(dāng)圖層發(fā)生變換時,這個點永遠(yuǎn)位于圖層變換之前anchorPoint的位置。當(dāng)改變一個圖層的position,你也改變了它的錨點,做3D變換的時候要時刻記住這一點,當(dāng)你視圖通過調(diào)整m34來讓它更加有3D效果,應(yīng)該首先把它放置于屏幕中央,然后通過平移來把它移動到指定位置(而不是直接改變它的position),這樣所有的3D圖層都共享一個錨點。
- 關(guān)于錨點(anchorPoint)與position
- position是決定了layer顯示在父控件的哪個位置,默認(rèn)position是該layer的中心點.
- anchorPoint決定了layer自身顯示在position的哪個位置.
- position與錨點的默認(rèn)點為中心點
- position總是與錨點顯示在同一位置
- layer是否發(fā)生偏移有錨點決定
- 3D仿射矩陣
3D仿射矩陣類似于2D仿射,3D仿射也有一個基礎(chǔ)矩陣,并且比2D的多一個維度
CGAffineTransform transform = CGAffineTransformIdentity;
結(jié)果:(CGAffineTransform) transform = (a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0)
[m11 m12 m13 m14]
[m21 m22 m23 m24]
[m31 m32 m33 m34]
[m41 m42 m43 m44]
矩陣的計算過程和2D類似,最后也能得到矩陣中每個位置的值對3D仿射效果的作用。我直接把結(jié)果貼出來。
平移因子: m41(x位置) m42(y位置) m43(z位置)
縮放因子: m11(x位置) m22(y位置)
切變因子: m21(x位置) m12(y位置)
旋轉(zhuǎn)因子: m13(x位置) m31(y位置)
透視因子: m34(有旋轉(zhuǎn)才能看出效果)
m34的默認(rèn)值是0,我們可以通過設(shè)置m34為(-1.0 / d)來應(yīng)用透視效果,d代表了視角相機和屏幕之間的距離,以像素為單位,那應(yīng)該如何計算這個距離呢?大概估算一個就行了。
因為視角相機實際上并不存在,所以可以根據(jù)屏幕上的顯示效果自由決定它放置的位置。通常500-1000就已經(jīng)很好了,但對于特定的圖層有時候更小后者更大的值會看起來更舒服,減少距離的值會增強透視效果,所以一個非常微小的值會讓它看起來更加失真,然而一個非常大的值會讓它基本失去透視效果
3D仿射常用的方法
//位移3D仿射
CATransform3DMakeTranslation
CATransform3DTranslation
//旋轉(zhuǎn)3D仿射:x-y-z軸的有個確定的范圍(介于-1 和+1之間)
CATransform3DMakeRotation
CATransform3DRotation
angle 是所需要變化的角度
x y z 設(shè)置為零表示不圍繞該軸做旋轉(zhuǎn),如果設(shè)置為1,則繞該軸做旋轉(zhuǎn)。
//縮放3D仿射
CATransform3DMakeScale
CATransform3DScale
//疊加3D仿射效果
CATransform3DConcat
//仿射基礎(chǔ)3D方法,可以直接做效果疊加
CGAffineTransformMake (sx,shx,shy,sy,tx,ty)
矩陣初始值:[ 1 0 0 0 1 0 0 0 1 0 0 0]
這個是一個初始化矩陣,帶入矩陣算法計算后的結(jié)構(gòu)會得到
x'=x , y'=y , z'=z
它的作用是清除之前對矩陣設(shè)置的仿射效果,或者用來初始化一個原始無效果的仿射矩陣
矩陣初始值:
[ 1 0 0 0 ]
[ 0 1 0 0 ]
[ 0 0 1 0 ]
[ 0 0 0 1 ]
//矩陣初始值。[ 1 0 0 0 1 0 0 0 1 0 0 0]
CATransform3DIdentity
//檢查是否有做過仿射3D效果
CATransform3DIsIdentity(transform)
//檢查是否是一個仿射3D效果
CATransform3DIsAffine(transform)
//檢查2個3D仿射效果是否相同
CATransform3DEqualToTransform(transform1,transform2)
//3D仿射效果反轉(zhuǎn)(反效果,比如原來擴大,就變成縮?。?CATransform3DInvert(transform)
//2D仿射轉(zhuǎn)換3D仿射
CATransform3DGetAffineTransform(transform)
CATransform3DMakeAffineTransform(transform)
CATransform3D與CGAffineTransform的轉(zhuǎn)換
//將一個CGAffinrTransform轉(zhuǎn)化為CATransform3DCATransform3D
CATransform3DMakeAffineTransform (CGAffineTransform m);
//判斷一個CATransform3D是否可以轉(zhuǎn)換為CAAffineTransformbool
CATransform3DIsAffine (CATransform3D t);
//將CATransform3D轉(zhuǎn)換為CGAffineTransformCGAffineTransform
CATransform3DGetAffineTransform (CATransform3D t);
當(dāng)我們改變過一個view.transform屬性或者view.layer.transform的時候需要恢復(fù)默認(rèn)狀態(tài)的話,記得先把他 們重置為:
view.transform = CGAffineTransformIdentity;
view.layer.transform = CATransform3DIdentity;
CALayer的transform屬性是是個CATransform3D類型的數(shù)據(jù),默認(rèn)值為CATransform3DIdentity。需要注意的是,CALayer是有隱式動畫的,如果你想關(guān)掉隱式動畫,用
[CATransaction setDisableActions:YES];
另外,要特地說明一下矩陣中的一個參數(shù)m34,m34影響透視效果.當(dāng)然,z方向上得有變化才會有透視效果,數(shù)值越大,透視效果越明顯.正值/負(fù)值都有意義,導(dǎo)致透視方向的不同。d越大,效果越不明顯,d越小,效果越明顯甚至導(dǎo)致失真。d的一個推薦的值是500-1000之間。
- 透視效果
#define RADIANS_TO_DEGREES(x) ((x)/M_PI*180.0)
CATransform3D transform = CATransform3DIdentity;
// 透視效果
transform.m34 = 0.0005;
transform = CATransform3DRotate(transform,(M_PI/180*40), 0, 1, 0);
[layer setTransform:transform];
注意我們使用的旋轉(zhuǎn)常量是`M_PI_4`,而不是你想象的`45`,因為iOS的變換函數(shù)使用弧度而不是角度作為單位。
弧度用數(shù)學(xué)常量`pi`的倍數(shù)表示,一個`pi`代表`180`度,所以四分之一的`pi`就是`45`度。
第二行一定要寫在第三行的前面;m34:透視效果m34= -1/M,M越小,透視效果越明顯,必須在有旋轉(zhuǎn)效果的前提下,才會看到透視效果。
- 合并兩個CATransform3D(CATransform3DConcat)
CATransform3D CATransform3DConcat ( CATransform3D a, CATransform3D b );
- CATransform3DInvert效果反轉(zhuǎn)(反效果,比如原來擴大,就變成縮小)
CATransform3D CATransform3DInvert ( CATransform3D t );
- CATransform3D與CGAffineTransform的轉(zhuǎn)換
//將一個CGAffinrTransform轉(zhuǎn)化為CATransform3DCATransform3D
CATransform3DMakeAffineTransform (CGAffineTransform m);
//判斷一個CATransform3D是否可以轉(zhuǎn)換為CAAffineTransformbool
CATransform3DIsAffine (CATransform3D t);
//將CATransform3D轉(zhuǎn)換為CGAffineTransformCGAffineTransform
CATransform3DGetAffineTransform (CATransform3D t);
- 減輕鋸齒-
shouldRasterize
屬性的默認(rèn)值是NO,可將其設(shè)置為YES來減輕鋸齒的效果。記得動畫結(jié)束時將其設(shè)置為NO。
self.menuViewController.view.layer.shouldRasterize = NO;
- content
設(shè)置CALayer的contents屬性為一個image來設(shè)置圖片,設(shè)置contentGravity來指定圖層內(nèi)容的拉伸方式。
iOS CATransform3D,旋轉(zhuǎn)之后出現(xiàn)的鋸齒處理方法
layer.shouldRasterize = YES;