iOS CALayer總結(jié)—圖層變換

今天我們來聊一聊圖層變換,很多動畫都是在變換的基礎(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' = d
y

這樣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' = sina
x + 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)擊事件中分別使用CGAffineTransformMakeCGAffineTransform對圖層做變換(對視圖的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í)行的效果是一樣的俐巴。

button1-效果圖.gif

button2-效果圖.gif

那是因?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)行的赎线。

button2-效果圖-2.gif

這就是CGAffineTransformMakeCGAffineTransform系列函數(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):


旋轉(zhuǎn)前.png
旋轉(zhuǎn)后.png

但是震嫉,其實(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)在再來看,是不是效果非常明顯:


效果圖.png

總結(jié)

圖層變換就先說到這了鸽凶,主要涉及到了2D和3D變換的一些原理和簡單的操作币砂。以后有機(jī)會,再做更深層次的研究吧玻侥。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末决摧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子凑兰,更是在濱河造成了極大的恐慌掌桩,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姑食,死亡現(xiàn)場離奇詭異波岛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)音半,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門则拷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贡蓖,“玉大人,你說我怎么就攤上這事煌茬〕馄蹋” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵坛善,是天一觀的道長晾蜘。 經(jīng)常有香客問我,道長眠屎,這世上最難降的妖魔是什么笙纤? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮组力,結(jié)果婚禮上省容,老公的妹妹穿的比我還像新娘。我一直安慰自己燎字,他們只是感情好腥椒,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著候衍,像睡著了一般笼蛛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛉鹿,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天滨砍,我揣著相機(jī)與錄音,去河邊找鬼妖异。 笑死惋戏,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的他膳。 我是一名探鬼主播响逢,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼棕孙!你這毒婦竟也來了舔亭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蟀俊,失蹤者是張志新(化名)和其女友劉穎钦铺,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肢预,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡矛洞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了误甚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缚甩。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡谱净,死狀恐怖窑邦,靈堂內(nèi)的尸體忽然破棺而出擅威,到底是詐尸還是另有隱情,我是刑警寧澤冈钦,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布郊丛,位于F島的核電站,受9級特大地震影響瞧筛,放射性物質(zhì)發(fā)生泄漏厉熟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一较幌、第九天 我趴在偏房一處隱蔽的房頂上張望揍瑟。 院中可真熱鬧,春花似錦乍炉、人聲如沸绢片。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽底循。三九已至,卻和暖如春槐瑞,著一層夾襖步出監(jiān)牢的瞬間熙涤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工困檩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留祠挫,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓悼沿,卻偏偏與公主長得像茸歧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子显沈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容