今天我們來聊一聊圖層變換,很多動畫都是在變換的基礎(chǔ)上完成的辆童,可以說變換是動畫的基礎(chǔ)宜咒。所以要想能夠很好的使用動畫,首先就需要對變換非常熟悉胸遇,大家都知道UIView有一個transform
屬性是用來做變換的荧呐,可以實(shí)現(xiàn)二維空間的平移、旋轉(zhuǎn)和縮放,實(shí)際上這也是對內(nèi)部圖層變換封裝倍阐。
一概疆、仿射變換
仿射
的意思是無論變換矩陣用什么值,圖層中平行的兩條線在變換之后任然保持平行峰搪,所以大家可以根據(jù)這個定義來判斷一個變換是不是仿射變換岔冀。
那么圖層的平移、旋轉(zhuǎn)和縮放到底是如何實(shí)現(xiàn)的呢概耻?首先使套,我們先來回憶一下大學(xué)線性代數(shù)中的矩陣乘法吧。
矩陣乘法:
計(jì)算規(guī)則:(1)當(dāng)矩陣A的列數(shù)等于矩陣B的行數(shù)是鞠柄,才可以計(jì)算
(2)計(jì)算的結(jié)果矩陣C的行數(shù)等于A的行數(shù)侦高,列數(shù)等于B的列數(shù)
(3)結(jié)果矩陣C的第 i 行第 j 列的元素Cij 等于矩陣A的第 i 行的元素與矩陣B的第 j 列對應(yīng)元素乘積之和。
舉例:假設(shè)兩個矩陣A和B:
A:
[1⊙岫拧2]
[2》钋骸1]
B:
[0 2『痪 3]
[1∏谱场1 2]
將兩個矩陣相乘得到矩陣C:
C = A * B =
[(1 * 0+2 * 1)〕孜铡(1 * 2+2 * 1)(1 * 3+2 * 2)]
[(2 * 0+1 * 1)∨夭邸(2 * 2+1 * 1)(2 * 3+1 * 2)]
= [2 4∪Ψ摹7]
∏胤蕖[1 5≡隆8]
下面我們來看仿射變換是怎樣使用矩陣計(jì)算來實(shí)現(xiàn)的:
在二維坐標(biāo)中小渊,我們將矩陣的坐標(biāo)點(diǎn)設(shè)置為:
A :
[x y 1]法褥,
仿射變換的基礎(chǔ)變換矩陣為:
B:
[a b 0]
c d 0
[tx ty 1]
通過A * B來得到一個變換之后的矩陣:
C = [ (ax+cy+tx) (bx+dy+ty) (1) ]
在這里茫叭,我們假設(shè)C = [x' y' 1];
那么我們就可以得到下面的等式:
x' = ax + cy + tx
y' = bx + dy + ty
平移
通過這兩個等式半等,我們可以發(fā)現(xiàn)揍愁,a,b,c,d在等于0或1的時候掷伙,會對結(jié)果又很大的影響焚刺。例如:
a = 1 ,b = 0 稠集,c = 0躯枢,d = 1
上面的等式為變成:
x' = x + tx
y' = y + ty
這樣x'和y‘就分別等于x和y加上一個常量孵奶,這樣的點(diǎn)C(x',y') 就相當(dāng)于點(diǎn)A(x,y) 在原來的基礎(chǔ)上平移了一段距離话肖。
CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)
就是通過這樣的計(jì)算實(shí)現(xiàn)的北秽。
將a = 1 ,b = 0 最筒,c = 0 贺氓,d = 1代入到我們的基礎(chǔ)變換矩陣中就可以得到仿射位移矩陣了:
[1 0 0]
0 1 0
[tx ty 1]
tx,ty分別對應(yīng)CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)
中的兩個參數(shù)。
我們平時常見的仿射變換還有縮放和旋轉(zhuǎn)床蜘,他們都是基于同樣的原理實(shí)現(xiàn)的辙培。
縮放:
將c,b,tx,ty 均置為0,上面的等式變?yōu)椋?/p>
x' = ax
y' = dy
這樣x'和y‘就分別等于x和y的a倍和b倍邢锯,從而實(shí)現(xiàn)了縮放的效果扬蕊。
將tx = 0 ,ty = 0 丹擎,c = 0 厨相,b = 0代入到我們的基礎(chǔ)變換矩陣中就可以得到仿射縮放矩陣了:
[a 0 0]
0 d 0
[0 0 1]
a,d分別對應(yīng)CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
方法中的參數(shù)sx和sy
旋轉(zhuǎn):
假設(shè)旋轉(zhuǎn)一個度數(shù)a:
a = cosa , b = sina , c = -sina , d = cosa , tx = 0 , ty = 0,上面的等式變?yōu)椋?/p>
x' = cosax - sina * y
y' = sinax + cosa * y
這樣得到的x’和y’就是x和y旋轉(zhuǎn)角度a之后得到的值鸥鹉。
將a = cosa , b = sina , c = -sina , d = cosa , tx = 0 , ty = 0代入到我們的基礎(chǔ)變換矩陣中就得到仿射旋轉(zhuǎn)矩陣:
[cosa sina 0]
-sina cosa 0
[0 0 1]
角度a對應(yīng)方法CGAffineTransformMakeRotation(CGFloat angle)
中的angle參數(shù)蛮穿。
以上變換都可以使用基礎(chǔ)變換函數(shù):CGAffineTransformMake(CGFloat a, CGFloat b, CGFloat c, CGFloat d, CGFloat tx, CGFloat ty)
帶入a,b,c,d,tx,ty的值得到變換結(jié)果。
混合變換
我們再使用CGAffineTransformMake系列方法在做變換的時候毁渗,每次做變換的時候都會清楚之前變換的效果践磅,也就是每次的變換都是以圖層的初始位置為參照點(diǎn)進(jìn)行的,但是如果我們希望視圖每次的變換都是在上一次變換的基礎(chǔ)上進(jìn)行的話灸异,那么怎么辦呢府适?
Core Graphics框架還為我們提供了一系列的函數(shù)可以在一個變換的基礎(chǔ)上做更深層次的變換。
方法如下:
CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)
為了了解的更清楚肺樟,我們還是直接上代碼看一下:
在視圖上創(chuàng)建一個view和兩個button檐春,view為進(jìn)行變化的視圖,button1和button2的點(diǎn)擊事件中分別使用CGAffineTransformMake
和CGAffineTransform
對圖層做變換(對視圖的transform和圖層的affineTransform屬性操作都能看到同樣的效果)么伯。
視圖的創(chuàng)建代碼我就不多說了疟暖,直接說變換:
在button1的點(diǎn)擊事件中執(zhí)行以下代碼:
- (IBAction)button1Click:(UIButton *)sender {
if (sender.isSelected) {
sender.selected = NO;
//通過平移,回到當(dāng)前視圖相對于frame的(0,0)位置
self.testView.transform = CGAffineTransformMakeTranslation(0,0);
}else {
sender.selected = YES;
//通過平移,平移到當(dāng)前視圖相對于frame的(0,50)位置
self.testView.transform = CGAffineTransformMakeTranslation(0,50);
}
}
在button1的點(diǎn)擊事件中執(zhí)行以下代碼:
- (IBAction)button2Click:(UIButton *)sender {
if (sender.isSelected) {
sender.selected = NO;
self.testView.layer.affineTransform = CGAffineTransformTranslate(CGAffineTransformIdentity, 0, 0);
}else {
sender.selected = YES;
self.testView.layer.affineTransform = CGAffineTransformTranslate(CGAffineTransformIdentity, 0, 50);
}
}
點(diǎn)擊button,我們發(fā)現(xiàn)田柔,兩種方式執(zhí)行的效果是一樣的俐巴。
那是因?yàn)槲覀兪鞘褂肅GAffineTransformTranslate時,是以
CGAffineTransformTranslate
為基礎(chǔ)進(jìn)行的硬爆,CGAffineTransformTranslate
是最初位置的中心點(diǎn)欣舵,每次改變都是基于這個中心點(diǎn)(也就是最初位置)進(jìn)行改變。我們將
CGAffineTransformIdentity
改為self.testView.transform
缀磕,我們會發(fā)現(xiàn)缘圈,在不停的點(diǎn)擊button2的時候劣光,self.testView
不斷的向下平移,這是因?yàn)槲覀兠看卧谶M(jìn)行平移的時候糟把,都是以上一次平移之后的位置進(jìn)行的赎线。
這就是
CGAffineTransformMake
和CGAffineTransform
系列函數(shù)的區(qū)別。
二糊饱、3D變換
我們上面所說到的不管是CGAffineTransformMake
系列函數(shù)還是CGAffineTransform
系列函數(shù)都是屬于Core Graphics框架垂寥,而Core Graphics是一個2D繪圖API,所以我們使用這兩種函數(shù)是無法進(jìn)行3D變換的另锋,但是滞项,我們在開發(fā)中關(guān)于圖層做3D變換的需求還是非常常見的,那么這個時候我們應(yīng)該如何處理呢夭坪?
在CALyer中文判,同樣存在一個transform
屬性,不過它是CATransform3D
類型室梅,用來做3D變換戏仓。之前,我們提到過圖層的zPosition
屬性亡鼠,transform
屬性就用用來操作zPosition
屬性來控制圖層靠近或者遠(yuǎn)離用戶的視角赏殃,從而達(dá)到3D變換的效果。
CATransform3D
也是一個矩陣间涵,但是和2x3的矩陣不同仁热,CATransform3D是一個可以在3維空間內(nèi)做變換的4x4的矩陣。
我們首先設(shè)置一個三維坐標(biāo)中勾哩,需要變換的坐標(biāo)點(diǎn):
A :
[x y z 1]抗蠢,z代表的就是zPosition
3D變換的基礎(chǔ)變換矩陣為:
B:
[m11 m21 m31 m41]
m12 m22 m32 m42
m13 m23 m33 m43
[m14 m24 m34 m44]
變換之后的坐標(biāo)點(diǎn)為:
A :
[x‘ y’ z‘ 1]
和CGAffineTransform
矩陣類似思劳,Core Animation提供了一系列的方法用來創(chuàng)建和組合CATransform3D
類型的矩陣迅矛,但是3D的平移和旋轉(zhuǎn)多處了一個z參數(shù),并且旋轉(zhuǎn)函數(shù)除了angle之外多出了x,y,z三個參數(shù)潜叛,分別決定了每個坐標(biāo)軸方向上的旋轉(zhuǎn)秽褒,各個方法如下:
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)
下面我們來做一個給圖層在Y軸方向旋轉(zhuǎn)45度的例子:
UIImageView *imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"image_1.jpg"]];
imageView.frame = CGRectMake(75, 100, 250, 250);
[self.view addSubview:imageView];
CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
imageView.layer.transform = transform;
但是通過效果圖,我么可以看到圖片并沒有旋轉(zhuǎn)效果钠导,看起來只不過是變窄了一點(diǎn):
但是震嫉,其實(shí)仔細(xì)想一下森瘪,在現(xiàn)實(shí)生活中牡属,如果我們用一個斜向的角度去看一個物體的時候,它確實(shí)會變窄扼睬,這就正確的逮栅。但是為什么現(xiàn)在的效果看起來并不是我們預(yù)期的呢悴势?那是因?yàn)椋矬w繞著Y軸旋轉(zhuǎn)時措伐,其中的一側(cè)會遠(yuǎn)離我們的視角特纤,理論上,物體遠(yuǎn)離我們的時候侥加,在我們的視角中捧存,物體應(yīng)該變小,所以物體遠(yuǎn)離我們的一側(cè)應(yīng)該比靠近我們的一側(cè)要短担败,但是現(xiàn)在并沒有這個效果昔穴,那么應(yīng)該怎樣實(shí)現(xiàn)這個效果呢?
在CALyer的顯示中提前,默認(rèn)使用是等距投影吗货,這種投影得到的遠(yuǎn)處的物體和近處的物體保持同樣的縮放比例,所以要想實(shí)現(xiàn)我們想要的效果狈网,我們需要使用透視投影宙搬。在上面提到的3D變換的基礎(chǔ)矩陣中,元素m34
就是用來控制透視投影效果的拓哺,元素m34
用于按比例縮放X和Y的值來計(jì)算到底要離視角多遠(yuǎn)勇垛。我們可以通過設(shè)置m34為-1.0 / d來應(yīng)用透視效果,d代表了想象中視角和屏幕之間的距離士鸥,單位為像素窥摄,通常情況下,d的值在500-1000之間础淤,看起來會比較舒服崭放。
將我們上面的代碼改為:
UIImageView *imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"image_1.jpg"]];
imageView.frame = CGRectMake(75, 100, 250, 250);
[self.view addSubview:imageView];
CATransform3D transform = CATransform3DIdentity;
transform.m34 = - 1.0 / 500.0;
transform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0);
imageView.layer.transform = transform;
現(xiàn)在再來看,是不是效果非常明顯:
總結(jié)
圖層變換就先說到這了鸽凶,主要涉及到了2D和3D變換的一些原理和簡單的操作币砂。以后有機(jī)會,再做更深層次的研究吧玻侥。