iOS動(dòng)畫-CALayer隱式動(dòng)畫原理與特性

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;
}

效果圖如下:


測(cè)試隱式動(dòng)畫.gif

經(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];  //出棧
}

效果圖如下:


測(cè)試隱式動(dòng)畫事務(wù).gif

可以看到,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;

翻譯過來大概就是說:

  1. 圖層會(huì)首先檢測(cè)它是否有委托,并且是否實(shí)現(xiàn)CALayerDelegate協(xié)議指定的-actionForLayer:forKey方法;如果有,就直接調(diào)用并返回結(jié)果。
  2. 如果沒有委托或者委托沒有實(shí)現(xiàn)-actionForLayer:forKey方法,圖層將會(huì)檢查包含屬性名稱對(duì)應(yīng)行為映射的actions字典
  3. 如果actions字典沒有包含對(duì)應(yīng)的屬性,圖層接著在它的style字典里搜索屬性名.
  4. 最后,如果在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)畫的方法有有了以下幾種方式:

  1. 使用UIView的動(dòng)畫函數(shù)(而不是依賴CATransaction)
  2. 繼承UIView,并覆蓋-actionforLayer:forkey:方法
  3. 直接創(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;
}

效果圖如下:


測(cè)試隱式動(dòng)畫-自定義圖層行為.gif

經(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使用詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容