Core Animation: CALayer 及其隱式動畫

要做出具有相當高的用戶體驗的 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,positionframe 其實是一個虛擬屬性川蒙,它并不實際存在蚜厉,而是通過 boundsposition計算得到的。他們的主要功能是描述視圖的位置屬性畜眨。UIVIew 中這三個屬性任何一個進行改變昼牛,實際上都是改變了對應layer的三個對應屬性。

除此之外康聂,CALayer 還有一個很關鍵的屬性anchorPoint-- 錨點匾嘱。它是一個單位量點,取值范圍在(0-1)之間早抠, 這個點在默認狀態(tài)下是(0.5霎烙,0.5),我們改變它會將整個視圖的位置偏移:

CALayer錨點

CALayer的 zPosition

一個很簡單的例子是蕊连,當我們在同一個位置覆蓋兩個 UIView 的時候悬垃,后添加的 View 將會覆蓋 底下的 View,這個順序?qū)Q定事件響應鏈的傳遞之機制甘苍。


綠色在下

如果我們設置底下 view 對應的 CALayer 的 zPosition 在原來的基礎上加1尝蠕,那么我們將看到:

設置zPosition

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;
}
實現(xiàn)了隱式動畫

隱式動畫就介紹到這里是嗜,CALayer 還有很多其他有用的特性愈案,有興趣的同學可以多多挖掘。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鹅搪,一起剝皮案震驚了整個濱河市站绪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌丽柿,老刑警劉巖恢准,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異甫题,居然都是意外死亡馁筐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門幔睬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來眯漩,“玉大人芹扭,你說我怎么就攤上這事麻顶。” “怎么了舱卡?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵辅肾,是天一觀的道長。 經(jīng)常有香客問我轮锥,道長矫钓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任舍杜,我火速辦了婚禮新娜,結果婚禮上,老公的妹妹穿的比我還像新娘既绩。我一直安慰自己概龄,他們只是感情好,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布饲握。 她就那樣靜靜地躺著私杜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪救欧。 梳的紋絲不亂的頭發(fā)上衰粹,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音笆怠,去河邊找鬼铝耻。 笑死,一個胖子當著我的面吹牛蹬刷,可吹牛的內(nèi)容都是我干的田篇。 我是一名探鬼主播替废,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼泊柬!你這毒婦竟也來了椎镣?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤兽赁,失蹤者是張志新(化名)和其女友劉穎状答,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刀崖,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡惊科,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了亮钦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馆截。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蜂莉,靈堂內(nèi)的尸體忽然破棺而出蜡娶,到底是詐尸還是另有隱情,我是刑警寧澤映穗,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布窖张,位于F島的核電站,受9級特大地震影響蚁滋,放射性物質(zhì)發(fā)生泄漏宿接。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一辕录、第九天 我趴在偏房一處隱蔽的房頂上張望睦霎。 院中可真熱鬧,春花似錦走诞、人聲如沸副女。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肮塞。三九已至,卻和暖如春姻锁,著一層夾襖步出監(jiān)牢的瞬間枕赵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工位隶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拷窜,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像篮昧,于是被迫代替她去往敵國和親赋荆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

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