Core Animation 基於一個假說, 屏幕上任何東西均可以(或者可能)作動畫. 動畫並不須要再Core Animation中手動打開, 相反的須要明確的關閉, 不然將一直存在.網絡
當改變CALayer的一個可作動畫的屬性, 改變並不會馬上在屏幕上體現出來, 它會從從前的值平滑的過渡到新的值. 這一切都是默認的行爲, 不須要咱們作任何操做.dom
這就是隱式動畫. 咱們並不須要指定動畫類型, 僅僅改變一個屬性, 而後Core Animation會決定如何而且什麼時候去作動畫. 實際上, 當改變一個屬性, 動畫執行時間取決於當前事務的設置, 動畫類型取決於圖層的行爲. 事務是Core Animation用來包含一系列屬性動畫集合的機制, 任何用指定事務去改變能夠作動畫的圖層的屬性都不會馬上發生改變, 而是當事務提交的時候, 開始一個動畫過分到新的值.函數
事務是經過CATransaction類來管理的, 這個類管理了一疊不能訪問的事務, 沒有屬性和實例方法, 而且不能alloc, init建立它, 可是能夠用+begin, +commit分別來入棧和出棧. 任何能夠作動畫的圖層屬性都會被添加到棧頂的事務, 能夠經過+setAnimationDuration:
設置當前事務的動畫時間, 或者 +animationDuration
來獲取值.oop
Core Animation在每一個run loop週期自動開始一次新的事務(run loop是iOS負責收集用戶輸入, 處理定時器或者網絡時事件而且從新繪製屏幕的東西), 即便不顯式的用[CATransaction begin]開始一次事務, 任何一個run loop中屬性的改變都會被集中起來, 而後作一次0.25秒的動畫.測試
#pragma mark - 測試事務控制CALayer動畫 - (void)changeButtonClick:(UIButton *)sender { // 開始一個新的事物 [CATransaction begin]; // 設置事務的 動畫執行時間 [CATransaction setAnimationDuration:1.f]; // 禁用動畫 [CATransaction setDisableActions:NO]; // 設置事務執行完成後的代碼塊 [CATransaction setCompletionBlock:^{ NSLog(@"Animation over"); self.colorLayer.affineTransform = CGAffineTransformRotate(self.colorLayer.affineTransform, M_PI_4); }]; //randomize the layer background color CGFloat red = arc4random() / (CGFloat)INT_MAX; CGFloat green = arc4random() / (CGFloat)INT_MAX; CGFloat blue = arc4random() / (CGFloat)INT_MAX; self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; sender.layer.backgroundColor = self.colorLayer.backgroundColor; // 提交事務和動畫 [CATransaction commit]; }
UIView 經過幾個類方法完成的動畫效果, 實際上就是都是因爲設置了 CATransaction的緣由.iOS4中, 蘋果味UIView的添加了基於block的動畫寫發, 事實上也是基於CATransaction的動畫.動畫
基於UIView的block的動畫容許你在動畫結束的時候提供一個完成的動做。CATranscation接口提供的+setCompletionBlock:方法也有一樣的功能。如上代碼所示.3d
如上第一節, 直接對CALayer對象修改動畫屬性都會引發動畫效果, 可是測試發現, 直接對UIView關聯的圖層作動畫而不是獨立的圖層, 這時候動畫效果消失了. 隱式動畫被UIView的關聯圖層給禁用了.代理
咱們把改變屬性時CALayer自動應用的動畫稱做行爲, 當CALayer的屬性被修改時候, 它會調用-actionForKey:
方法, 傳遞屬性的名稱. 剩下的操做在CALayer頭文件中有詳細的說明, 有如下幾個步驟:code
actionForLayer:forKey:
方法, 若是有, 直接調用並返回結果.actions
字典.actions字典
沒有包含對應的屬性, 那麼圖層接着在它的style字典中搜索屬性名.style
裏面也找不到對應的行爲, 那麼圖層將會直接調用定義了每一個屬性標準行爲的-defaultActionForKey:
方法.因此通過一輪的搜索, -actionForKey:
要麼返回空(這時候將不會有動畫發生), 要麼是CAAction協議對應的對象, 最後CALayer拿着這個結果去對先前和當前的值作動畫.orm
UIKit如何禁用隱式動畫: 每一個UIView對它關聯的圖層都扮演了一個委託, 而且提供了-actionForLayer:forKey:
的實現方法. 當不在一個動畫塊的實現中, UIView對全部圖層都返回nil, 可是在block範圍以內, 就返回一個非空值. 因而咱們能夠預言, 當屬性在動畫塊以外發生改變, UIView直接經過返回nil來禁用隱式動畫. 但若是在動畫塊範圍以內, 根據動畫具體類型返回相應的屬性.
CATransaction有個方法叫作: +setDisableActions:
, 能夠用來對全部屬性打開或者關閉隱式動畫. 在 [CATransaction begin]
以後添加[CATransaction setDisableActions:YES];
代碼, 就能夠阻止動畫的發生.
總結一下:
-actionForLayer:forKey:
方法, 或者直接建立一個顯式動畫.-actionForLayer:forKey:
委託方法, 或者提供一個actions字典來控制隱式動畫.對於單獨的圖層, 設置actions
字典要比實現代理更加簡單一點. 行爲一般是一個被Core Animation隱式調用的顯式動畫對象, 好比使用CATransition
, 由於它能夠相應CAAction
協議, 而且能夠做爲圖層行爲. 經過此對象, 咱們能夠自定義隱式動畫的動畫樣式. demo:
#pragma mark - 測試過渡動畫 - (void)changeButtonClick1:(UIButton *)sender { CATransition *transition = [CATransition animation]; transition.type = kCATransitionPush; transition.subtype = kCATransitionFromLeft; self.colorLayer.actions = @{@"backgroundColor": transition}; //randomize the layer background color CGFloat red = arc4random() / (CGFloat)INT_MAX; CGFloat green = arc4random() / (CGFloat)INT_MAX; CGFloat blue = arc4random() / (CGFloat)INT_MAX; self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; sender.layer.backgroundColor = self.colorLayer.backgroundColor; }
CALayer的屬性行爲很不尋常, 由於改變一個圖層的屬性而沒有當即生效, 而是經過一段時間漸變動新, 這是怎麼作到的呢? 這一小節作簡單的筆記.
當改變圖層屬性時候, 屬性值是當即被更新的(當讀取它的數據時候, 會發現它的值在設置那一刻就已經生效了), 可是屏幕並無當即發生變化. 這是由於你設置的屬性並無直接調整圖層的外觀, 相反, 它只是定義了圖層動畫結束以後將要改變的外觀.
當設置CALayer的屬性, 其實是在定義噹噹前事務結果時候圖層將如何顯示的模型. Core Animation扮演一個控制器的角色, 而且負責根據圖層行爲和事務設置去不斷更新視圖的這些屬性在屏幕上的狀態.
在iOS中, 屏幕每秒鐘別重繪60次. 若是動畫時長比六十分之一秒要長, CoreAnimation就須要在設置新值和新值生效之間, 對屏幕圖層進行從新組織, 就意味着, CALayer除了真實值, 以外, 必須誰知道當前顯示在屏幕上的屬性值的記錄.
每一個圖層屬性的顯示值都被存儲在一個叫作呈現圖層的獨立圖層中, 能夠經過-presentationLayer
方法來訪問. 這個呈現圖層其實是模型圖層的複製, 可是它的屬性值, 表明了在任何指定時刻當前外觀的效果. 換句話說, 你能夠經過呈現圖層的值來獲取當前屏幕上真正顯示出來的值.
除了圖層樹, 還有呈現樹, 呈現樹是經過圖層樹的呈現圖層造成的. 呈現樹僅僅是圖層首次被提交(第一次在屏幕上顯示)的時候建立, 因此在那以前調用 -presentationLayer
將會返回nil.
通常狀況下, 咱們不須要直接訪問呈現圖層, 咱們通常都經過和模型圖層的交互讓CoreAnimation更顯顯示. 但有時候仍是須要用到呈現圖層:
-hitTest:
方法來判斷圖層是否被觸摸, 這時候使用呈現圖層而不是模型圖層調用-hitTest:
會更有意義, 由於呈現圖層表明用戶當前看到的位置, 而不是動畫結束以後的位置.小demo:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { CGPoint point = [[touches anyObject] locationInView:self.view]; if ([self.moveLayer.presentationLayer hitTest:point]) { CGFloat red = arc4random() / (CGFloat)INT_MAX; CGFloat green = arc4random() / (CGFloat)INT_MAX; CGFloat blue = arc4random() / (CGFloat)INT_MAX; [CATransaction begin]; [CATransaction setAnimationDuration:1]; self.moveLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1].CGColor; [CATransaction commit]; }else{ [CATransaction begin]; [CATransaction setAnimationDuration:4]; self.moveLayer.position = point; [CATransaction commit]; } }