iOS動畫-CALayer隱式動畫原理與特性 一、何爲隱式動畫? 二、隱式動畫的原理 三、關閉和開啓隱式動畫 四、自定義圖層行爲

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;

翻譯過來大概就是說:

  1. 圖層會首先檢測它是否有委託,並且是否實現CALayerDelegate協議指定的-actionForLayer:forKey方法;如果有,就直接調用並返回結果。
  2. 如果沒有委託或者委託沒有實現-actionForLayer:forKey方法,圖層將會檢查包含屬性名稱對應行爲映射的actions字典
  3. 如果actions字典沒有包含對應的屬性,圖層接着在它的style字典裏搜索屬性名.
  4. 最後,如果在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關聯的圖層禁用了隱式動畫,那麼對這種圖層做動畫的方法有有了以下幾種方式:

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

其實,對於單獨存在的圖層,我們也可以通過實現圖層的-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使用詳解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章