Core Animation的一個非常顯著的特性是就是實現動畫,而且它支持隱式動畫和顯式動畫兩種形式,本篇我們主要從隱式動畫說起;
本篇主要內容:
1.何爲隱式動畫
2.隱式動畫原理-事務與圖層行爲
3.隱式動畫的關閉與顯示
4.隱式動畫自定義圖層行爲
一、何爲隱式動畫?
Core Animation是基於這樣的一個假設:屏幕上的任何東西都可以(或者可能)做動畫,它並不需要手動打開,反而是需要我們明確的關閉,否則動畫會一直存在。所謂隱式動畫,其實是指我們可以在不設定任何動畫類型的情況下,僅僅改變CALayer的一個可做動畫的屬性,就能實現動畫效果。
這聽起來似乎不太真實,我們可以通過下面的代碼來驗證,使用隨機色修改了CALayer的背景色:
@interface TestLayerAnimationVC ()
@property (nonatomic,strong) CALayer *colorLayer;
@end
- (void)viewDidLoad {
[super viewDidLoad];
_colorLayer = [[CALayer alloc] init];
_colorLayer.frame = CGRectMake(30, 30, kDeviceWidth - 60, 60);
[self.view.layer addSublayer:_colorLayer];
}
- (IBAction)changeColor:(UIButton *)sender{
CGFloat red = arc4random() % 255 / 255.0;
CGFloat green = arc4random() % 255 / 255.0;
CGFloat blue = arc4random() % 255 / 255.0;
UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
_colorLayer.backgroundColor = randomColor.CGColor;
}
效果圖如下:
經過測試,我們會發現每次設置的顏色並不是立刻在屏幕上跳變出來,相反,它是從先前的值平滑過渡到新的值,這一切都是默認行爲,你不需要做額外的操作,這就是隱式動畫。
二、隱式動畫的原理
當我們改變一個CALayer屬性時,Core Animation是如何判斷動畫類型和持續時間呢?實際上動畫執行的時間取決於當前事務的設置,動畫類型則取決於圖層行爲。
1.事務
事務,其實是Core Animation用來包含一系列屬性動畫集合的機制,通過指定事務來改變圖層的可動畫屬性,這些變化都不是立刻發生變化的,而是在事務被提交的時候才啓動一個動畫過渡到新值。任何可以做動畫的圖層屬性都會被添加到棧頂的事務。
事務是通過CATransaction類來做管理,它沒有屬性或者實例方法,而且也不能通過alloc和init去創建它,它的常用操作如下:
//1.動畫屬性的入棧
+ (void)begin;
//2.動畫屬性出棧
+ (void)commit;
//3.設置當前事務的動畫時間
+ (void)setAnimationDuration:(CFTimeInterval)dur;
//4.獲取當前事務的動畫時間
+ (CFTimeInterval)animationDuration;
//5.在動畫結束時提供一個完成的動作
+ (void)setCompletionBlock:(nullable void (^)(void))block;
現在再來考慮隱式動畫,其實是Core Animation在每個RunLoop週期中會自動開始一次新的事務,即使你不顯式的使用[CATranscation begin]開始一次事務,任何在一次RunLoop運行時循環中屬性的改變都會被集中起來,執行默認0.25秒的動畫。
現在,我們就通過事務來設置動畫做一個驗證,代碼如下:
- (IBAction)changeColor:(UIButton *)sender{
[CATransaction begin]; //入棧
//1.設置動畫執行時間
[CATransaction setAnimationDuration:3];
//2.設置動畫執行完畢後的操作:顏色漸變之後再旋轉90度
[CATransaction setCompletionBlock:^{
CGAffineTransform transform = self.colorLayer.affineTransform;
transform = CGAffineTransformRotate(transform, M_PI_2);
self.colorLayer.affineTransform = transform;
}];
CGFloat red = arc4random() % 255 / 255.0;
CGFloat green = arc4random() % 255 / 255.0;
CGFloat blue = arc4random() % 255 / 255.0;
UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
_colorLayer.backgroundColor = randomColor.CGColor;
[CATransaction commit]; //出棧
}
效果圖如下:
可以看到,CALayer顏色的漸變動畫已經變爲了3秒,而旋轉動畫由於是默認事務變化,仍然以0.25秒快速執行。
2.圖層行爲
我們上述的實驗對象是一個獨立圖層,如果直接對UIView或者CALayer關聯的圖層layer改變動畫屬性,這樣是沒有隱式動畫效果的,這說明雖然Core Animation對所有的CALayer動畫屬性設置了隱式動畫,但UIView把它關聯的圖層的這個特性給關閉了。
爲了更好的理解中一點,我們需要知道隱式動畫是如何實現的:
我們把改變屬性時CALayer自動執行的動畫稱作行爲,當CALayer的屬性被修改時,它會調用-actionForKey:方法傳遞屬性名稱,我們可以找到這個方法的具體說明如下:
/* Returns the action object associated with the event named by the
* string 'event'. The default implementation searches for an action
* object in the following places:
*
* 1. if defined, call the delegate method -actionForLayer:forKey:
* 2. look in the layer's `actions' dictionary
* 3. look in any `actions' dictionaries in the `style' hierarchy
* 4. call +defaultActionForKey: on the layer's class
*
* If any of these steps results in a non-nil action object, the
* following steps are ignored. If the final result is an instance of
* NSNull, it is converted to `nil'. */
- (nullable id<CAAction>)actionForKey:(NSString *)event;
翻譯過來大概就是說:
- 圖層會首先檢測它是否有委託,並且是否實現CALayerDelegate協議指定的-actionForLayer:forKey方法;如果有,就直接調用並返回結果。
- 如果沒有委託或者委託沒有實現-actionForLayer:forKey方法,圖層將會檢查包含屬性名稱對應行爲映射的actions字典
- 如果actions字典沒有包含對應的屬性,圖層接着在它的style字典裏搜索屬性名.
- 最後,如果在style也找不到對應的行爲,那麼圖層將會直接調用定義了每個屬性的標準行爲的+defaultActionForKey:方法
從流程上分析來看,經過一次完整的搜索動畫之後,-actionForKey:要麼返回空(這種情況不會有動畫發生),要麼返回遵循CAAction協議的對象(CALayer拿這個結果去對先前和當前的值做動畫)。現在我們再來考慮UIKit是如何禁用隱式動畫的:
每個UIView對它關聯的圖層都遵循了CALayerDelegate協議,並且實現了-actionForLayer:forKey方法。當不在一個動畫塊中修改動畫屬性時,UIView對所有圖層行爲都返回了nil,但是在動畫Block範圍就返回了非空值,下面通過一段代碼來驗證:
@interface TestLayerAnimationVC ()
@property (nonatomic,weak)IBOutlet UIView *layerView;
@end
- (void)viewDidLoad {
[super viewDidLoad];
//測試圖層行爲:UIKit默認關閉了自身關聯圖層的隱式動畫
NSLog(@"OutSide:%@",[self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
[UIView beginAnimations:nil context:nil];
NSLog(@"InSide:%@",[self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
[UIView commitAnimations];
}
//打印:
OutSide:<null>
InSide:<CABasicAnimation: 0x600001703100>
由此得出結論:當屬性在動畫塊之外發生變化,UIView直接通過返回nil來禁用隱式動畫。但是如果在動畫塊範圍內,UIView則會根據動畫具體類型返回響應的屬性,
三、關閉和開啓隱式動畫
當然,返回nil並不是禁用隱式動畫的唯一方法,CATransaction也爲我們提供了具體的方法,可以用來對所有屬性打開或者關閉隱式動畫,方法如下:
+ (void)setDisableActions:(BOOL)flag;
UIView關聯的圖層禁用了隱式動畫,那麼對這種圖層做動畫的方法有有了以下幾種方式:
- 使用UIView的動畫函數(而不是依賴CATransaction)
- 繼承UIView,並覆蓋-actionforLayer:forkey:方法
- 直接創建顯式動畫
其實,對於單獨存在的圖層,我們也可以通過實現圖層的-actionforLayer:forkey:方法,或者提供一個actions字典來控制隱式動畫
四、自定義圖層行爲
通過對事務和圖層行爲的瞭解,我們可以這樣思考,圖層行爲其實是被Core Animation隱式調用的顯式動畫對象。我們可以發現改變隱式動畫的這種圖層行爲有兩種方式:
1.給layer設置自定義的actions字典
2.實現委託代理,返回遵循CAAction協議的動畫對象
現在,我們嘗試使用第一種方法來自定義圖層行爲,這裏用到的是一個推進過渡的動畫(也是遵循了CAAction的動畫類),具體的代碼如下:
@interface TestLayerAnimationVC ()
@property (nonatomic,strong) CALayer *colorLayer;
@end
- (void)viewDidLoad {
[super viewDidLoad];
_colorLayer = [[CALayer alloc] init];
_colorLayer.frame = CGRectMake(30, 30, kDeviceWidth - 60, 60);
_colorLayer.backgroundColor = [UIColor orangeColor].CGColor;
//自定義動畫對象
CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromLeft;
_colorLayer.actions = @{@"backgroundColor":transition};
[self.view.layer addSublayer:_colorLayer];
}
- (IBAction)changeColor:(UIButton *)sender{
CGFloat red = arc4random() % 255 / 255.0;
CGFloat green = arc4random() % 255 / 255.0;
CGFloat blue = arc4random() % 255 / 255.0;
UIColor *randomColor = [UIColor colorWithRed:red green:green blue:blue alpha:1];
_colorLayer.backgroundColor = randomColor.CGColor;
}
效果圖如下:
經測試,我們會看到colorLayer將會以從左到右推進過渡的形式改變色值;我們通過給layer設置自定義的actions字典實現了自定義的圖層行爲;
---End---
相關文章:
iOS動畫-CALayer寄宿圖與繪製原理
iOS動畫-CALayer佈局屬性詳解
iOS動畫-CALayer隱式動畫原理與特性
iOS動畫-CAAnimation使用詳解