要做出具有相當高的用戶體驗的 APP喉前,適當?shù)氖褂脛赢嫳夭豢缮僦急稹OS 中的動畫有很多種诗赌,從其他的博文中,你可能會找到很多諸如基礎動畫
秸弛、關鍵幀動畫
, 轉場動畫
等等铭若。這些動畫其實都是基于 CALayer 的變換和隱式動畫的組合。這篇文章將詳細介紹 CALayer 和它的隱式動畫递览。
一叼屠、CALayer
CALayer 是一個很基礎的類,它被包含在 Core Animation 中绞铃,Core Animation 不能根據(jù)它的字面意思來理解镜雨,它的真實身份來源于 Layer Kit ,動畫只是它的一部分儿捧,并且是很小的一部分荚坞。Core Animation 是一個復合引擎,它的存在目的是幫助系統(tǒng)以盡可能快的速度將屏幕上不同的可視內(nèi)容組合起來菲盾,存放于圖層樹的體系中颓影。我們在 iOS 上看到的一切東西都由 Core Animation 來呈現(xiàn),包括動畫懒鉴。CALayer 作為 Core Animation 中基本的圖層诡挂,它提供了可視化功能碎浇,我們使用的 UIView 本身不可見,只是因為其內(nèi)部承載了一個 CALayer 才具有可見功能璃俗。而 UIView 則提供了響應事件的能力奴璃,本質(zhì)上,UIView 負責事件鏈的傳遞和響應城豁,CALayer 則負責視圖的呈現(xiàn)苟穆。
CALayer 之所以能夠顯示內(nèi)容,因為它包含一個 contents 屬性唱星,這個屬性的類型雖然為 id
類型鞭缭,但是,如果我們填入了非圖片類型的數(shù)據(jù)之后魏颓,會使得 CALayer 顯示空白岭辣。CALayer的contents接受來自其他的(通常是 Core Graphics)繪制內(nèi)容組成的圖片,加入其中甸饱,這個圖片被稱之為寄宿圖沦童。
每一個 UIView 都有一個 CALayer 相聯(lián)系, UIView 默認是對應 CALayer 的代理叹话,我們通過 UIView 的 layer 屬性就能夠訪問到它偷遗。大體上,UIView 已經(jīng)很完善了驼壶,不僅能夠看見氏豌,還能夠進行用戶交互,對于更加底層的 CALayer热凹,我們是在沒有過多接觸的必要泵喘。但不代表我們不會使用到它 —— CALayer 相比 UIView 具有如下優(yōu)勢:
- 設置視圖的圓角、陰影般妙、邊框
- 3D變換
- 不規(guī)則的形狀
- 透明遮罩
以上的功能纪铺,在日常的開發(fā)中或多或少的會使用到。而這些通過 CALayer 可很容易的做到碟渺,UIView 并沒有這些功能鲜锚。
CALayer 繪制
同時在繪制方面,UIView 也是基于 CALayer 來進行的苫拍。 通常我們會在代碼中使用
- (void) DrawRect;
為 UIView 進行重繪芜繁。當我們實現(xiàn)了這個方法的時候,它會在 UIView 呈現(xiàn)的時候被調(diào)用绒极】チ睿或者我們也可以主動使用-(void)setNeedsDisplay
方法來間接調(diào)用, 另外,在 UIView 的 bounds 被修改的時候集峦,這個重繪方法也會被調(diào)用伏社。
根本原因是,UIView 被當作 CALayer 的代理塔淤,調(diào)用 - (void) DrawRect;
將自動運行 CALayer 的繪制功能代碼摘昌,也就是說, UIView 幫我們將如何調(diào)用 CALayer 繪制的動作給做了高蜂。
CALayerDelegate的代理方法中聪黎,有以下兩個方法:
-(void)displayLayer:(CAlayer)layer;
-(void)drawLayer:(CALayer)layer inContet:(CgContentRef)ctx;
-(void)displayLayer:(CAlayer)layer;
可以給layer接受一個來自于 UIView 的繪制內(nèi)容,我們也可以不實現(xiàn)這個方法备恤,那么代理會繼續(xù)調(diào)用下面的-(void)drawLayer:(CALayer)layer inContet:(CgContentRef)ctx;
方法稿饰,這個方法中將直接繪制內(nèi)容到 layer 上。當然這些工作不需要我們自己來做露泊,UIView 會替我們完成喉镰,我們做的僅僅是實現(xiàn)UIView的- (void) DrawRect;
就可以了。
我們可以自己設置 layer 的代理惭笑,然后進行代理方法的實現(xiàn)侣姆,這樣也能得到和 UIView 自動完成代理一樣的效果。
坐標系
之前說了沉噩,UIView 的一切可視化都來自于它的 layer捺宗,因此包括 UIView 的frame
,bounds
,center
內(nèi)容都來自CALayer, 這三個屬性在CALayer中分別對應CALayer的屬性:frame
,bounds
,position
。frame
其實是一個虛擬屬性川蒙,它并不實際存在蚜厉,而是通過 bounds
和position
計算得到的。他們的主要功能是描述視圖的位置屬性畜眨。UIVIew 中這三個屬性任何一個進行改變昼牛,實際上都是改變了對應layer的三個對應屬性。
除此之外康聂,CALayer 還有一個很關鍵的屬性anchorPoint
-- 錨點匾嘱。它是一個單位量點,取值范圍在(0-1)之間早抠, 這個點在默認狀態(tài)下是(0.5霎烙,0.5),我們改變它會將整個視圖的位置偏移:
CALayer的 zPosition
一個很簡單的例子是蕊连,當我們在同一個位置覆蓋兩個 UIView 的時候悬垃,后添加的 View 將會覆蓋 底下的 View,這個順序?qū)Q定事件響應鏈的傳遞之機制甘苍。
如果我們設置底下 view 對應的 CALayer 的 zPosition
在原來的基礎上加1尝蠕,那么我們將看到:
zPosition表示的是圖層的層級屬性數(shù)值,每個圖層的該默認值都是0.如果有一個圖層比0大载庭,哪怕是0.001看彼,這個圖層就會被推到最前面廊佩。
但是要注意的是,盡管 zPosition 改變了圖層的顯示順序靖榕,卻對每個視圖之間的事件傳遞沒有影響标锄。上圖中,綠色矩形原先被覆蓋的部分會收到紅色矩形的影響茁计,盡管我們從視覺上看到它在上面料皇。所以,事件的響應和圖層的顯示順序并沒有必然的聯(lián)系星压。
CALayer 的事件處理
Core Animation 沒有給 CALayer 添加事件處理機制践剂,我們不能使用它來進行交互,如果要使用交互的話娜膘,我們可以使用兩個方法檢測用戶的交互事件:
- (Bool) containPoint;
- (CALayer*)hitTest;
- (Bool) containPoint;
將檢測一個 layer 是不是包含了某個點逊脯,它返回一個 Bool 值。而 - (CALayer*)hitTest;
則檢測當前點處在某個 layer 之上竣贪,并將 layer 返回男窟。
我們可以通過這兩個方法,結合 屏幕Touch回調(diào)函數(shù)來檢測用戶的交互事件贾富。
更多 CALayer 的使用知識歉眷,在我的另一篇博文中有介紹:
http://www.cnblogs.com/FBiOSBlog/p/6900534.html
二、CALayer 的隱式動畫
Core Animation中颤枪,假設 CALayer 每個屬性的改動都是有動畫的(如果有可能)汗捡。對于單獨的 CALayer,每一個屬性的的改動畏纲,如果存在對應的屬性動畫扇住,則會被實現(xiàn)并展現(xiàn)出來。因為 CALayer 這種特質(zhì)的動畫不需要進行任何動畫代碼的修飾就能夠體現(xiàn)動畫效果盗胀,所以這些動畫被統(tǒng)稱為隱式動畫艘蹋。但是CALayer 的隱式動畫在 UIView 中不會被觸發(fā), 更確切地說是被 UIVIew 禁用了票灰。
為了證明 UIView 禁用了隱式動畫的事實女阀,我寫了一段代碼:
_layer = [CALayer layer];
_layer.frame = CGRectMake(100, 100, 100, 100);
_layer.backgroundColor = [UIColor blueColor].CGColor;
_view = [[UIView alloc]initWithFrame:CGRectMake(220, 100, 100, 100)];
_view.backgroundColor = [UIColor blueColor];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.frame = CGRectMake([UIScreen mainScreen].bounds.size.width/2.0 - 50, 250, 100, 50);
[btn setTitle:@"點擊" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(changeColor) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_view];
[self.view addSubview:btn];
[self.view.layer addSublayer:_layer];
}
- (void)changeColor{
UIColor *color = [UIColor colorWithRed:random()%255/256.0 green:random()%255/256.0 blue:random()%255/256.0 alpha:1];
// 設置 隱式動畫的時長
[CATransaction setAnimationDuration:1];
_layer.backgroundColor = color.CGColor;
_view.backgroundColor = color;
}
效果:同樣的都是改變視圖的顏色,CALayer 會自動執(zhí)行動畫屑迂,(其中為了區(qū)別浸策,我將隱式動畫的事件拉長了,隱式動畫的默認時長為0.25s)惹盼,但是 UIView 卻沒有動畫庸汗,盡管兩者都沒有任何其他的有關動畫代碼的修飾,這就是我們所說的 CALayer 的隱式動畫手报。和隱式動畫相對的還有顯示動畫蚯舱,顯示動畫的使用其實是在隱式動畫的基礎上加上動畫的事務 CATransaction 改化,CATransaction 是一個很特殊的類,它不提供任何初始化的方法枉昏,只暴露了部分 API陈肛,它的使用目的是讓我們手動設置隱式動畫為顯式動畫,并能夠改動動畫的效果凶掰。比如上面的代碼:[CATransaction setAnimationDuration:1];
就是改變了動畫的時長。我們?nèi)绻褂?CATransaction 構成顯式動畫可以這樣做:
//給CALayer 開放顯示動畫
[CATransaction begin];
[CATransaction setAnimationDuration:1.5];
_layer.backgroundColor = color.CGColor;
[CATransaction commit];
按照我們之前說的內(nèi)容蜈亩,UIView 是通過 CALayer 的顯示特性來呈現(xiàn)視圖的懦窘,既然 CALayer 具有隱式動畫效果,UIView 應該也能夠獲取這個特性才是稚配,原因是它將隱式動畫效果禁用了畅涂。 UIView 中,默認禁用隱式動畫道川,但是我們可以通過 UIView 的一些 API 來開放這些動畫午衰。將上面的修改 UIView 顏色的那部分代碼修改:
//給 UIVIew 添加動畫
[UIView beginAnimations:@"animation" context:nil];
[UIView setAnimationDuration:1.5];
_view.backgroundColor = color;
[UIView commitAnimations];
再看看效果:由此可見,UIView 并非不能執(zhí)行動畫冒萄,而是需要在特定的環(huán)境中進行臊岸。iOS 中,我們除了使用 [UIView beginAnimations:@"animation" context:nil];
和 [UIView commitAnimations];
之外尊流,還有一系列的其他手段開啟動畫帅戒。像 UIView 的Block動畫 animationWithDuration:animations:
將動畫的內(nèi)容放在 block 中,以避免開發(fā)者動畫開始到提交的閉合操作失誤崖技。但不管是哪一種動畫逻住,其內(nèi)部都會調(diào)用CALayer 的事務 CATransaction 來進行相關的設置。
動畫在APP的合理使用能夠提升用戶的體驗迎献,但是也會有部分性能的消耗瞎访,最主要的是,我們需要在合適的場景使用動畫才更有意義吁恍,所以將隱式動畫的開啟開關交給開發(fā)者更為可靠扒秸。
那 UIView 禁用隱式動畫的原理是什么呢?
在 CALayer 中冀瓦,所有的動畫被統(tǒng)稱為: action —— 行為鸦采。CALayer 每次執(zhí)行隱式動畫之前,會執(zhí)行一個函數(shù):
- actionForkey()
這個函數(shù)內(nèi)部其實做了以下事情:
- 首先查看layer本身是否有委托咕幻,并且檢查這個委托是否實現(xiàn)了
- actionForLayer: forKey
代理方法渔伯,如果有這個方法,直接調(diào)用并返回結果肄程。 否則進入到下一步锣吼。 - layer 接下來將會檢查包含屬性名對應行為映射的字典 actions选浑。 actions 字典以屬性名為key,以屬性的行為為 value 存儲玄叠。這是將決定一個屬性是否有動畫的過程之一古徒。如果 actions 沒有屬性名,則到下一步读恃。
- 除了 actions 字典隧膘,layer 還有一個 style 字典,這個字典中包含了一些固定的類型對應的動畫寺惫,這是對不太好在 actions 定義的屬性行為的補充疹吃。
- 最后,如果在上述中都找不到行為西雀,那么圖層調(diào)用每個屬性對應的標準行為萨驶。也就是隱式動畫的本身。
我們在使用 CATransaction 事務對動畫做出修改的時候艇肴,實際上是在 actions 字典中添加行為腔呜,它具有比標準行為更高的優(yōu)先級,一次每次改動總能生效再悼。
我們知道 UIView 默認是對應的 CALayer 的代理核畴,禁用動畫的方式就是實現(xiàn)- actionForLayer: forKey
代理方法,并在方法中直接返回 nil冲九,這樣動畫就生生被切掉了膛檀。
當我們使用了 UIView 的動畫 API 的時候,這段期間 - actionForLayer: forKey
方法將返回我們實現(xiàn)的動畫內(nèi)容娘侍。這就是為什么我們調(diào)用這些 API 能夠顯示動畫的原因了咖刃。那么如何給 UIView 永久開啟隱式動畫我們就有了思路了。
給 UIView 永久開啟隱式動畫憾筏。
其實開啟隱式動畫的方式我們已經(jīng)做過了嚎杨,就是在 UIView 的動畫 API 中書寫動畫有關的代碼。如果想要像 CALayer 一樣氧腰,不需要其他的 API 也能給予 UIView 動畫的話枫浙,我們或許可以創(chuàng)建一個類,繼承自 UIView古拴,并重寫它的- actionForLayer: forKey
方法箩帚。當然這么做,可能有點麻煩黄痪,你可能更傾向于直接使用一個顯示的動畫吧紧帕。不過我們試試總歸是可以的,我創(chuàng)建了一個MyAnimationView 類,它繼承自 UIView ,然后實現(xiàn)它的- actionForLayer: forKey
方法:
#import "MyAnimationView.h"
@implementation MyAnimationView
- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;{
return [layer animationForKey:event];
}
@end
然后就像平常使用View一樣使用它:
- (void)viewDidLoad {
[super viewDidLoad];
_view = [[MyAnimationView alloc]initWithFrame:CGRectMake(220, 100, 100, 100)];
_view.backgroundColor = [UIColor blueColor];
[self.view addSubview:_view];
btn.frame = CGRectMake([UIScreen mainScreen].bounds.size.width/2.0 - 50, 250, 100, 50);
[btn setTitle:@"點擊" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(changeColor) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
- (void)changeColor{
UIColor *color = [UIColor colorWithRed:random()%255/256.0 green:random()%255/256.0 blue:random()%255/256.0 alpha:1];
// 這里我并沒有添加任何和動畫有關的代碼 直接修改顏色
_view.backgroundColor = color;
}
隱式動畫就介紹到這里是嗜,CALayer 還有很多其他有用的特性愈案,有興趣的同學可以多多挖掘。