Core Animation的一個(gè)非常顯著的特性是就是實(shí)現(xiàn)動(dòng)畫,而且它支持隱式動(dòng)畫和顯式動(dòng)畫兩種形式,本篇我們主要從隱式動(dòng)畫說起;
本篇主要內(nèi)容:
1.何為隱式動(dòng)畫
2.隱式動(dòng)畫原理-事務(wù)與圖層行為
3.隱式動(dòng)畫的關(guān)閉與顯示
4.隱式動(dòng)畫自定義圖層行為
一、何為隱式動(dòng)畫?
Core Animation是基于這樣的一個(gè)假設(shè):屏幕上的任何東西都可以(或者可能)做動(dòng)畫,它并不需要手動(dòng)打開,反而是需要我們明確的關(guān)閉,否則動(dòng)畫會(huì)一直存在。所謂隱式動(dòng)畫,其實(shí)是指我們可以在不設(shè)定任何動(dòng)畫類型的情況下,僅僅改變CALayer的一個(gè)可做動(dòng)畫的屬性,就能實(shí)現(xiàn)動(dòng)畫效果。
這聽起來似乎不太真實(shí),我們可以通過下面的代碼來驗(yàn)證,使用隨機(jī)色修改了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;
}
效果圖如下:

經(jīng)過測(cè)試,我們會(huì)發(fā)現(xiàn)每次設(shè)置的顏色并不是立刻在屏幕上跳變出來,相反,它是從先前的值平滑過渡到新的值,這一切都是默認(rèn)行為,你不需要做額外的操作,這就是隱式動(dòng)畫。
二、隱式動(dòng)畫的原理
當(dāng)我們改變一個(gè)CALayer屬性時(shí),Core Animation是如何判斷動(dòng)畫類型和持續(xù)時(shí)間呢?實(shí)際上動(dòng)畫執(zhí)行的時(shí)間取決于當(dāng)前事務(wù)的設(shè)置,動(dòng)畫類型則取決于圖層行為。
1.事務(wù)
事務(wù),其實(shí)是Core Animation用來包含一系列屬性動(dòng)畫集合的機(jī)制,通過指定事務(wù)來改變圖層的可動(dòng)畫屬性,這些變化都不是立刻發(fā)生變化的,而是在事務(wù)被提交的時(shí)候才啟動(dòng)一個(gè)動(dòng)畫過渡到新值。任何可以做動(dòng)畫的圖層屬性都會(huì)被添加到棧頂?shù)氖聞?wù)。
事務(wù)是通過CATransaction類來做管理,它沒有屬性或者實(shí)例方法,而且也不能通過alloc和init去創(chuàng)建它,它的常用操作如下:
//1.動(dòng)畫屬性的入棧
+ (void)begin;
//2.動(dòng)畫屬性出棧
+ (void)commit;
//3.設(shè)置當(dāng)前事務(wù)的動(dòng)畫時(shí)間
+ (void)setAnimationDuration:(CFTimeInterval)dur;
//4.獲取當(dāng)前事務(wù)的動(dòng)畫時(shí)間
+ (CFTimeInterval)animationDuration;
//5.在動(dòng)畫結(jié)束時(shí)提供一個(gè)完成的動(dòng)作
+ (void)setCompletionBlock:(nullable void (^)(void))block;
現(xiàn)在再來考慮隱式動(dòng)畫,其實(shí)是Core Animation在每個(gè)RunLoop周期中會(huì)自動(dòng)開始一次新的事務(wù),即使你不顯式的使用[CATranscation begin]開始一次事務(wù),任何在一次RunLoop運(yùn)行時(shí)循環(huán)中屬性的改變都會(huì)被集中起來,執(zhí)行默認(rèn)0.25秒的動(dòng)畫。
現(xiàn)在,我們就通過事務(wù)來設(shè)置動(dòng)畫做一個(gè)驗(yàn)證,代碼如下:
- (IBAction)changeColor:(UIButton *)sender{
[CATransaction begin]; //入棧
//1.設(shè)置動(dòng)畫執(zhí)行時(shí)間
[CATransaction setAnimationDuration:3];
//2.設(shè)置動(dòng)畫執(zhí)行完畢后的操作:顏色漸變之后再旋轉(zhuǎn)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顏色的漸變動(dòng)畫已經(jīng)變?yōu)榱?秒,而旋轉(zhuǎn)動(dòng)畫由于是默認(rèn)事務(wù)變化,仍然以0.25秒快速執(zhí)行。
2.圖層行為
我們上述的實(shí)驗(yàn)對(duì)象是一個(gè)獨(dú)立圖層,如果直接對(duì)UIView或者CALayer關(guān)聯(lián)的圖層layer改變動(dòng)畫屬性,這樣是沒有隱式動(dòng)畫效果的,這說明雖然Core Animation對(duì)所有的CALayer動(dòng)畫屬性設(shè)置了隱式動(dòng)畫,但UIView把它關(guān)聯(lián)的圖層的這個(gè)特性給關(guān)閉了。
為了更好的理解中一點(diǎn),我們需要知道隱式動(dòng)畫是如何實(shí)現(xiàn)的:
我們把改變屬性時(shí)CALayer自動(dòng)執(zhí)行的動(dòng)畫稱作行為,當(dāng)CALayer的屬性被修改時(shí),它會(huì)調(diào)用-actionForKey:方法傳遞屬性名稱,我們可以找到這個(gè)方法的具體說明如下:
/* 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;
翻譯過來大概就是說:
- 圖層會(huì)首先檢測(cè)它是否有委托,并且是否實(shí)現(xiàn)CALayerDelegate協(xié)議指定的-actionForLayer:forKey方法;如果有,就直接調(diào)用并返回結(jié)果。
- 如果沒有委托或者委托沒有實(shí)現(xiàn)-actionForLayer:forKey方法,圖層將會(huì)檢查包含屬性名稱對(duì)應(yīng)行為映射的actions字典
- 如果actions字典沒有包含對(duì)應(yīng)的屬性,圖層接著在它的style字典里搜索屬性名.
- 最后,如果在style也找不到對(duì)應(yīng)的行為,那么圖層將會(huì)直接調(diào)用定義了每個(gè)屬性的標(biāo)準(zhǔn)行為的+defaultActionForKey:方法
從流程上分析來看,經(jīng)過一次完整的搜索動(dòng)畫之后,-actionForKey:要么返回空(這種情況不會(huì)有動(dòng)畫發(fā)生),要么返回遵循CAAction協(xié)議的對(duì)象(CALayer拿這個(gè)結(jié)果去對(duì)先前和當(dāng)前的值做動(dòng)畫)?,F(xiàn)在我們?cè)賮砜紤]UIKit是如何禁用隱式動(dòng)畫的:
每個(gè)UIView對(duì)它關(guān)聯(lián)的圖層都遵循了CALayerDelegate協(xié)議,并且實(shí)現(xiàn)了-actionForLayer:forKey方法。當(dāng)不在一個(gè)動(dòng)畫塊中修改動(dòng)畫屬性時(shí),UIView對(duì)所有圖層行為都返回了nil,但是在動(dòng)畫Block范圍就返回了非空值,下面通過一段代碼來驗(yàn)證:
@interface TestLayerAnimationVC ()
@property (nonatomic,weak)IBOutlet UIView *layerView;
@end
- (void)viewDidLoad {
[super viewDidLoad];
//測(cè)試圖層行為:UIKit默認(rèn)關(guān)閉了自身關(guān)聯(lián)圖層的隱式動(dòng)畫
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>
由此得出結(jié)論:當(dāng)屬性在動(dòng)畫塊之外發(fā)生變化,UIView直接通過返回nil來禁用隱式動(dòng)畫。但是如果在動(dòng)畫塊范圍內(nèi),UIView則會(huì)根據(jù)動(dòng)畫具體類型返回響應(yīng)的屬性,
三、關(guān)閉和開啟隱式動(dòng)畫
當(dāng)然,返回nil并不是禁用隱式動(dòng)畫的唯一方法,CATransaction也為我們提供了具體的方法,可以用來對(duì)所有屬性打開或者關(guān)閉隱式動(dòng)畫,方法如下:
+ (void)setDisableActions:(BOOL)flag;
UIView關(guān)聯(lián)的圖層禁用了隱式動(dòng)畫,那么對(duì)這種圖層做動(dòng)畫的方法有有了以下幾種方式:
- 使用UIView的動(dòng)畫函數(shù)(而不是依賴CATransaction)
- 繼承UIView,并覆蓋-actionforLayer:forkey:方法
- 直接創(chuàng)建顯式動(dòng)畫
其實(shí),對(duì)于單獨(dú)存在的圖層,我們也可以通過實(shí)現(xiàn)圖層的-actionforLayer:forkey:方法,或者提供一個(gè)actions字典來控制隱式動(dòng)畫
四、自定義圖層行為
通過對(duì)事務(wù)和圖層行為的了解,我們可以這樣思考,圖層行為其實(shí)是被Core Animation隱式調(diào)用的顯式動(dòng)畫對(duì)象。我們可以發(fā)現(xiàn)改變隱式動(dòng)畫的這種圖層行為有兩種方式:
1.給layer設(shè)置自定義的actions字典
2.實(shí)現(xiàn)委托代理,返回遵循CAAction協(xié)議的動(dòng)畫對(duì)象
現(xiàn)在,我們嘗試使用第一種方法來自定義圖層行為,這里用到的是一個(gè)推進(jìn)過渡的動(dòng)畫(也是遵循了CAAction的動(dòng)畫類),具體的代碼如下:
@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;
//自定義動(dòng)畫對(duì)象
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;
}
效果圖如下:

經(jīng)測(cè)試,我們會(huì)看到colorLayer將會(huì)以從左到右推進(jìn)過渡的形式改變色值;我們通過給layer設(shè)置自定義的actions字典實(shí)現(xiàn)了自定義的圖層行為;
---End---
相關(guān)文章:
iOS動(dòng)畫-CALayer寄宿圖與繪制原理
iOS動(dòng)畫-CALayer布局屬性詳解
iOS動(dòng)畫-CALayer隱式動(dòng)畫原理與特性
iOS動(dòng)畫-CAAnimation使用詳解