圖層行為 現在來做個實驗,試著直接對UIView關聯的圖層做動畫而不是一個單獨的圖層。清單7.4是對清單7.2代碼的一點修改,移除了colorLayer,並且直接設置layerView關聯圖層的背景色。 清單7.4 直接設置圖層的屬性 1 @interface ViewController () 2 ...
圖層行為
現在來做個實驗,試著直接對UIView關聯的圖層做動畫而不是一個單獨的圖層。清單7.4是對清單7.2代碼的一點修改,移除了colorLayer
,並且直接設置layerView
關聯圖層的背景色。
清單7.4 直接設置圖層的屬性
1 @interface ViewController () 2 3 @property (nonatomic, weak) IBOutlet UIView *layerView; 4 5 @end 6 7 @implementation ViewController 8 9 - (void)viewDidLoad 10 { 11 [super viewDidLoad]; 12 //set the color of our layerView backing layer directly 13 self.layerView.layer.backgroundColor = [UIColor blueColor].CGColor; 14 } 15 16 - (IBAction)changeColor 17 { 18 //begin a new transaction 19 [CATransaction begin]; 20 //set the animation duration to 1 second 21 [CATransaction setAnimationDuration:1.0]; 22 //randomize the layer background color 23 CGFloat red = arc4random() / (CGFloat)INT_MAX; 24 CGFloat green = arc4random() / (CGFloat)INT_MAX; 25 CGFloat blue = arc4random() / (CGFloat)INT_MAX; 26 self.layerView.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; 27 //commit the transaction 28 [CATransaction commit]; 29 }View Code
運行程式,你會發現當按下按鈕,圖層顏色瞬間切換到新的值,而不是之前平滑過渡的動畫。發生了什麼呢?隱式動畫好像被UIView
關聯圖層給禁用了。
試想一下,如果UIView
的屬性都有動畫特性的話,那麼無論在什麼時候修改它,我們都應該能註意到的。所以,如果說UIKit建立在Core Animation(預設對所有東西都做動畫)之上,那麼隱式動畫是如何被UIKit禁用掉呢?
我們知道Core Animation通常對CALayer
的所有屬性(可動畫的屬性)做動畫,但是UIView
把它關聯的圖層的這個特性關閉了。為了更好說明這一點,我們需要知道隱式動畫是如何實現的。
我們把改變屬性時CALayer
自動應用的動畫稱作行為,當CALayer
的屬性被修改時候,它會調用-actionForKey:
方法,傳遞屬性的名稱。剩下的操作都在CALayer
的頭文件中有詳細的說明,實質上是如下幾步:
- 圖層首先檢測它是否有委托,並且是否實現
CALayerDelegate
協議指定的-actionForLayer:forKey
方法。如果有,直接調用並返回結果。 - 如果沒有委托,或者委托沒有實現
-actionForLayer:forKey
方法,圖層接著檢查包含屬性名稱對應行為映射的actions
字典。 - 如果
actions字典
沒有包含對應的屬性,那麼圖層接著在它的style
字典接著搜索屬性名。 - 最後,如果在
style
裡面也找不到對應的行為,那麼圖層將會直接調用定義了每個屬性的標準行為的-defaultActionForKey:
方法。
所以一輪完整的搜索結束之後,-actionForKey:
要麼返回空(這種情況下將不會有動畫發生),要麼是CAAction
協議對應的對象,最後CALayer
拿這個結果去對先前和當前的值做動畫。
於是這就解釋了UIKit是如何禁用隱式動畫的:每個UIView
對它關聯的圖層都扮演了一個委托,並且提供了-actionForLayer:forKey
的實現方法。當不在一個動畫塊的實現中,UIView
對所有圖層行為返回nil
,但是在動畫block範圍之內,它就返回了一個非空值。我們可以用一個demo做個簡單的實驗(清單7.5)
清單7.5 測試UIView的actionForLayer:forKey:
實現
1 @interface ViewController () 2 3 @property (nonatomic, weak) IBOutlet UIView *layerView; 4 5 @end 6 7 @implementation ViewController 8 9 - (void)viewDidLoad 10 { 11 [super viewDidLoad]; 12 //test layer action when outside of animation block 13 NSLog(@"Outside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]); 14 //begin animation block 15 [UIView beginAnimations:nil context:nil]; 16 //test layer action when inside of animation block 17 NSLog(@"Inside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]); 18 //end animation block 19 [UIView commitAnimations]; 20 } 21 22 @endView Code
運行程式,控制台顯示結果如下:
1 $ LayerTest[21215:c07] Outside: <null> 2 $ LayerTest[21215:c07] Inside: <CABasicAnimation: 0x757f090>View Code
於是我們可以預言,當屬性在動畫塊之外發生改變,UIView
直接通過返回nil
來禁用隱式動畫。但如果在動畫塊範圍之內,根據動畫具體類型返回相應的屬性,在這個例子就是CABasicAnimation
(第八章“顯式動畫”將會提到)。
當然返回nil
並不是禁用隱式動畫唯一的辦法,CATransacition
有個方法叫做+setDisableActions:
,可以用來對所有屬性打開或者關閉隱式動畫。如果在清單7.2的[CATransaction begin]
之後添加下麵的代碼,同樣也會阻止動畫的發生:
[CATransaction setDisableActions:YES];
總結一下,我們知道瞭如下幾點
UIView
關聯的圖層禁用了隱式動畫,對這種圖層做動畫的唯一辦法就是使用UIView
的動畫函數(而不是依賴CATransaction
),或者繼承UIView
,並覆蓋-actionForLayer:forKey:
方法,或者直接創建一個顯式動畫(具體細節見第八章)。- 對於單獨存在的圖層,我們可以通過實現圖層的
-actionForLayer:forKey:
委托方法,或者提供一個actions
字典來控制隱式動畫。
我們來對顏色漸變的例子使用一個不同的行為,通過給colorLayer
設置一個自定義的actions
字典。我們也可以使用委托來實現,但是actions
字典可以寫更少的代碼。那麼到底改如何創建一個合適的行為對象呢?
行為通常是一個被Core Animation隱式調用的顯式動畫對象。這裡我們使用的是一個實現了CATransaction
的實例,叫做推進過渡。
第八章中將會詳細解釋過渡,不過對於現在,知道CATransition
響應CAAction
協議,並且可以當做一個圖層行為就足夠了。結果很贊,不論在什麼時候改變背景顏色,新的色塊都是從左側滑入,而不是預設的漸變效果。
清單7.6 實現自定義行為
1 @interface ViewController () 2 3 @property (nonatomic, weak) IBOutlet UIView *layerView; 4 @property (nonatomic, weak) IBOutlet CALayer *colorLayer;/*熱心人發現這裡應該改為@property (nonatomic, strong) CALayer *colorLayer;否則運行結果不正確。 5 */ 6 7 @end 8 9 @implementation ViewController 10 11 - (void)viewDidLoad 12 { 13 [super viewDidLoad]; 14 15 //create sublayer 16 self.colorLayer = [CALayer layer]; 17 self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f); 18 self.colorLayer.backgroundColor = [UIColor blueColor].CGColor; 19 //add a custom action 20 CATransition *transition = [CATransition animation]; 21 transition.type = kCATransitionPush; 22 transition.subtype = kCATransitionFromLeft; 23 self.colorLayer.actions = @{@"backgroundColor": transition}; 24 //add it to our view 25 [self.layerView.layer addSublayer:self.colorLayer]; 26 } 27 28 - (IBAction)changeColor 29 { 30 //randomize the layer background color 31 CGFloat red = arc4random() / (CGFloat)INT_MAX; 32 CGFloat green = arc4random() / (CGFloat)INT_MAX; 33 CGFloat blue = arc4random() / (CGFloat)INT_MAX; 34 self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; 35 } 36 37 @end 38View Code