iOS動畫-CALayer隱式動畫原理與特性

Core Animation的一個非常顯著的特性是就是實現(xiàn)動畫,而且它支持隱式動畫和顯式動畫兩種形式,本篇我們主要從隱式動畫說起;

本篇主要內(nèi)容:
1.何為隱式動畫
2.隱式動畫原理-事務(wù)與圖層行為
3.隱式動畫的關(guān)閉與顯示
4.隱式動畫自定義圖層行為

一、何為隱式動畫?

Core Animation是基于這樣的一個假設(shè):屏幕上的任何東西都可以(或者可能)做動畫,它并不需要手動打開,反而是需要我們明確的關(guān)閉,否則動畫會一直存在。所謂隱式動畫,其實是指我們可以在不設(shè)定任何動畫類型的情況下,僅僅改變CALayer的一個可做動畫的屬性,就能實現(xiàn)動畫效果。
這聽起來似乎不太真實,我們可以通過下面的代碼來驗證,使用隨機色修改了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;
}

效果圖如下:


測試隱式動畫.gif

經(jīng)過測試,我們會發(fā)現(xiàn)每次設(shè)置的顏色并不是立刻在屏幕上跳變出來,相反,它是從先前的值平滑過渡到新的值,這一切都是默認(rèn)行為,你不需要做額外的操作,這就是隱式動畫。

二、隱式動畫的原理

當(dāng)我們改變一個CALayer屬性時,Core Animation是如何判斷動畫類型和持續(xù)時間呢?實際上動畫執(zhí)行的時間取決于當(dāng)前事務(wù)的設(shè)置,動畫類型則取決于圖層行為。

1.事務(wù)

事務(wù),其實是Core Animation用來包含一系列屬性動畫集合的機制,通過指定事務(wù)來改變圖層的可動畫屬性,這些變化都不是立刻發(fā)生變化的,而是在事務(wù)被提交的時候才啟動一個動畫過渡到新值。任何可以做動畫的圖層屬性都會被添加到棧頂?shù)氖聞?wù)。

事務(wù)是通過CATransaction類來做管理,它沒有屬性或者實例方法,而且也不能通過alloc和init去創(chuàng)建它,它的常用操作如下:

//1.動畫屬性的入棧
+ (void)begin;

//2.動畫屬性出棧
+ (void)commit;

//3.設(shè)置當(dāng)前事務(wù)的動畫時間
+ (void)setAnimationDuration:(CFTimeInterval)dur;

//4.獲取當(dāng)前事務(wù)的動畫時間
+ (CFTimeInterval)animationDuration;

//5.在動畫結(jié)束時提供一個完成的動作
+ (void)setCompletionBlock:(nullable void (^)(void))block;

現(xiàn)在再來考慮隱式動畫,其實是Core Animation在每個RunLoop周期中會自動開始一次新的事務(wù),即使你不顯式的使用[CATranscation begin]開始一次事務(wù),任何在一次RunLoop運行時循環(huán)中屬性的改變都會被集中起來,執(zhí)行默認(rèn)0.25秒的動畫。
現(xiàn)在,我們就通過事務(wù)來設(shè)置動畫做一個驗證,代碼如下:

- (IBAction)changeColor:(UIButton *)sender{
    [CATransaction begin];  //入棧
    //1.設(shè)置動畫執(zhí)行時間
    [CATransaction setAnimationDuration:3];
    //2.設(shè)置動畫執(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];  //出棧
}

效果圖如下:


測試隱式動畫事務(wù).gif

可以看到,CALayer顏色的漸變動畫已經(jīng)變?yōu)榱?秒,而旋轉(zhuǎn)動畫由于是默認(rèn)事務(wù)變化,仍然以0.25秒快速執(zhí)行。

2.圖層行為

我們上述的實驗對象是一個獨立圖層,如果直接對UIView或者CALayer關(guān)聯(lián)的圖層layer改變動畫屬性,這樣是沒有隱式動畫效果的,這說明雖然Core Animation對所有的CALayer動畫屬性設(shè)置了隱式動畫,但UIView把它關(guān)聯(lián)的圖層的這個特性給關(guān)閉了。
為了更好的理解中一點,我們需要知道隱式動畫是如何實現(xiàn)的:
我們把改變屬性時CALayer自動執(zhí)行的動畫稱作行為,當(dāng)CALayer的屬性被修改時,它會調(diào)用-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;

翻譯過來大概就是說:

  1. 圖層會首先檢測它是否有委托,并且是否實現(xiàn)CALayerDelegate協(xié)議指定的-actionForLayer:forKey方法;如果有,就直接調(diào)用并返回結(jié)果。
  2. 如果沒有委托或者委托沒有實現(xiàn)-actionForLayer:forKey方法,圖層將會檢查包含屬性名稱對應(yīng)行為映射的actions字典
  3. 如果actions字典沒有包含對應(yīng)的屬性,圖層接著在它的style字典里搜索屬性名.
  4. 最后,如果在style也找不到對應(yīng)的行為,那么圖層將會直接調(diào)用定義了每個屬性的標(biāo)準(zhǔn)行為的+defaultActionForKey:方法

從流程上分析來看,經(jīng)過一次完整的搜索動畫之后,-actionForKey:要么返回空(這種情況不會有動畫發(fā)生),要么返回遵循CAAction協(xié)議的對象(CALayer拿這個結(jié)果去對先前和當(dāng)前的值做動畫)?,F(xiàn)在我們再來考慮UIKit是如何禁用隱式動畫的:
每個UIView對它關(guān)聯(lián)的圖層都遵循了CALayerDelegate協(xié)議,并且實現(xiàn)了-actionForLayer:forKey方法。當(dāng)不在一個動畫塊中修改動畫屬性時,UIView對所有圖層行為都返回了nil,但是在動畫Block范圍就返回了非空值,下面通過一段代碼來驗證:

@interface TestLayerAnimationVC ()

@property (nonatomic,weak)IBOutlet UIView *layerView;

@end

- (void)viewDidLoad {
    [super viewDidLoad];
   //測試圖層行為:UIKit默認(rèn)關(guān)閉了自身關(guān)聯(lián)圖層的隱式動畫
    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)屬性在動畫塊之外發(fā)生變化,UIView直接通過返回nil來禁用隱式動畫。但是如果在動畫塊范圍內(nèi),UIView則會根據(jù)動畫具體類型返回響應(yīng)的屬性,

三、關(guān)閉和開啟隱式動畫

當(dāng)然,返回nil并不是禁用隱式動畫的唯一方法,CATransaction也為我們提供了具體的方法,可以用來對所有屬性打開或者關(guān)閉隱式動畫,方法如下:

+ (void)setDisableActions:(BOOL)flag;

UIView關(guān)聯(lián)的圖層禁用了隱式動畫,那么對這種圖層做動畫的方法有有了以下幾種方式:

  1. 使用UIView的動畫函數(shù)(而不是依賴CATransaction)
  2. 繼承UIView,并覆蓋-actionforLayer:forkey:方法
  3. 直接創(chuàng)建顯式動畫

其實,對于單獨存在的圖層,我們也可以通過實現(xiàn)圖層的-actionforLayer:forkey:方法,或者提供一個actions字典來控制隱式動畫

四、自定義圖層行為

通過對事務(wù)和圖層行為的了解,我們可以這樣思考,圖層行為其實是被Core Animation隱式調(diào)用的顯式動畫對象。我們可以發(fā)現(xiàn)改變隱式動畫的這種圖層行為有兩種方式:
1.給layer設(shè)置自定義的actions字典
2.實現(xiàn)委托代理,返回遵循CAAction協(xié)議的動畫對象
現(xiàn)在,我們嘗試使用第一種方法來自定義圖層行為,這里用到的是一個推進(jìn)過渡的動畫(也是遵循了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;
}

效果圖如下:


測試隱式動畫-自定義圖層行為.gif

經(jīng)測試,我們會看到colorLayer將會以從左到右推進(jìn)過渡的形式改變色值;我們通過給layer設(shè)置自定義的actions字典實現(xiàn)了自定義的圖層行為;

---End---
相關(guān)文章:
iOS動畫-CALayer寄宿圖與繪制原理
iOS動畫-CALayer布局屬性詳解
iOS動畫-CALayer隱式動畫原理與特性
iOS動畫-CAAnimation使用詳解

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

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