圖層行為(隱式動畫),圖層行為
圖層行為
現在來做個實驗,試著直接對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)viewDidLoad10 {11 [super viewDidLoad];12 //set the color of our layerView backing layer directly13 self.layerView.layer.backgroundColor = [UIColor blueColor].CGColor;14 }15 16 - (IBAction)changeColor17 {18 //begin a new transaction19 [CATransaction begin];20 //set the animation duration to 1 second21 [CATransaction setAnimationDuration:1.0];22 //randomize the layer background color23 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 transaction28 [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)viewDidLoad10 {11 [super viewDidLoad];12 //test layer action when outside of animation block13 NSLog(@"Outside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);14 //begin animation block15 [UIView beginAnimations:nil context:nil];16 //test layer action when inside of animation block17 NSLog(@"Inside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);18 //end animation block19 [UIView commitAnimations];20 }21 22 @end
View 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 ViewController10 11 - (void)viewDidLoad12 {13 [super viewDidLoad];14 15 //create sublayer16 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 action20 CATransition *transition = [CATransition animation];21 transition.type = kCATransitionPush;22 transition.subtype = kCATransitionFromLeft;23 self.colorLayer.actions = @{@"backgroundColor": transition};24 //add it to our view25 [self.layerView.layer addSublayer:self.colorLayer];26 }27 28 - (IBAction)changeColor29 {30 //randomize the layer background color31 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 @end38
View Code