你所不知道的CALayer隱式動畫及事務

淺談CALayer的隱式動畫及事務

一、前言

本文是為了后續直播App送禮大動畫實戰演練做鋪墊, 淺談CALayer的隱式動畫及事務.

全文主要涉及以下問題:

  • CALayer圖層的定義
  • 何為隱式動畫?
  • 所有CALayer都有隱私動畫嗎? UIView的backing layer呢?
  • 哪些對象遵循CAAction協議
  • 動畫事務是什么?

二、 CALayer

CALayer圖層, 是數據模型, 數據對象. 對于iOS平臺, 一個UIView視圖在展示之前, 系統都會為其創建一個支持圖層(backing layer), 其中就儲存了View外貌樣式的表現內容, 比如圖層通過contents屬性來管理bitmap位圖, 從而充當位圖的容器.

Backing layer的delegate(CALayerDelegate)就是該圖層所屬的view對象.

三、 隱式動畫

我們平常都肯定使用過顯式動畫, 但我們比較少見到的隱式動畫(implicit animation)又是什么.

從Core Animation Guide官方指南中, 并沒有找到隱式動畫的明確定義, 僅提及了以下相關內容:

  1. 直接更改圖層CALayer的屬性就會觸發隱式動畫, 但是修改UIView對象支持圖層(backing layer)的動畫屬性是不會發生隱式動畫的, 因為UIView默認禁止了backing layer的隱式動畫, 所以對backing layer屬性的修改在UIView上的反應是直接變化, 沒有平滑過度的動畫效果
  2. 隱式動畫會使用當前動畫事務的參數默認值來執行動畫
  3. 隱式動畫會直接更改了layer模型中的值, 而顯式動畫不會(在動畫執行完后, 會根據layer模型的屬性進行"還原", 所以我們在添加動畫后需要手動修改layer的屬性來確保動畫完成時, 圖層能夠"還原"到動畫結束時的位置! 只有presentationLayer中的值會隨著動畫執行不斷改變.)
  4. 隱式動畫執行過程中無法直接被移除, 而顯式動畫可以通過實例方法removeAnimationForKey:或removeAllAnimations來直接移除
  5. Core Animation通過 遵循CAAction協議的對象 來實現隱式動畫

此處來一發示例, 幫助我們理解一下上方第1點:

/*
 現有UIView對象 viewA, 以及一個CALayer對象 layerB, 我們把 layerB 添加到 
 viewA 的 layer(這個就叫backing layer) 上.
*/

// 對backing layer進行屬性修改, 不會發生隱式動畫, 而是由之前的顏色直接變成紅色   
viewA.layer.backgroundColor = [UIColor redColor].CGColor;

// layerB作為viewA.layer的子圖層, 會發生隱式動畫, layerB的顏色會由原先的顏色平滑過渡到藍色
layerB.backgroundColor = [UIColor blueColor].CGColor;

至此, 想必我們已經清楚CALayer何時會發生隱式動畫了, 但后半句中說UIView的backing layer不會有隱式動畫又是何解?

這里需要關聯第5點中提到的遵循CAAction協議的對象

實際上, CAAnimation及其派生類, 如CABasicAnimation、CASpringAnimation, 以及CATransition都遵循并實現了 CAAction 協議中的方法. 當CALayer的動畫屬性改變時就會去查找匹配的CAAniamtion對象來執行動畫. 其查找流程用代碼形式表示如下:

/*
1. 若返回遵循 CAAction 協議對象, 則用其執行動畫(runActionForKey:object:arguments:)
2. 若該方法返回nil, 不執行隱式動畫, 直接更新屬性
*/
- (id<CAAction>)searchActionForLayer:(CALayer *)layer forKey:(NSString *)key {
    id<CAAction> action = nil;
    // 先問代理該key對應的Action, 如果返回不為nil, 則已找到; 如果返回nil, 則繼續執行查找邏輯; 如果返回NSNull對象則停止剩余查找邏輯, 最終返回NSNull
    if ([layer.delegate respondsToSelector:@selector(actionForLayer:forKey:)]) {
        id<CAAction> action = [layer.delegate actionForLayer:layer forKey:key];
        if ([NSNull null] == action) {
            return nil;
        }
        else if (action) {
            return action;
        }
    }
    if (!action) {
        action = [self actionForKey:key in:layer.actions];
    }
    if (!action) {
        for (NSDictionary<NSString*, id<CAAction>> *actions in layer.style) {
            if ((action = [self actionForKey:key in:actions])) {
                break;
            }
        }
    }
    if (!action) {
        action = [[layer class] defaultActionForKey:key];
    }
    return action;
}

- (id<CAAction>)actionForKey:(NSString *)key in:(NSDictionary<NSString*, id<CAAction>> *)actions {
    for (NSString *actionKey in actions.allKeys) {
        if ([actionKey isEqualToString:key]) {
            return actions[actionKey];
        }
    }
    return nil;
}

UIView作為backing layer的delegate(as CALayerDelegate), 實現了-actionForLayer:forKey方法; 當不處于動畫block范圍內時, 該方法返回nil; 否則, 返回對應的動畫對象. 關聯上方函數注釋若該方法返回nil, 不執行隱式動畫, 直接更新屬性, 這就是為什么UIView的backing layer不會有隱式動畫.

四、 動畫事務

動畫事務, 也類似于數據庫事務, 用于組合某個邏輯里邊的一系列操作. Core Animation會自動為我們某個圖層的單個或多個顯式動畫、隱式動畫創建隱式事務. 當然, 我們也可以通過 CATransaction 的類方法, 來顯式創建事務.

// 顯式事務
[CATransaction begin];
[CATransaction setValue:@(0.3) forKey:kCATransactionAnimationDuration];
layer.opacity = 0.0;
[CATransaction commit];

// UIView提供用來做動畫的類方法等價于上方顯式創建動畫, 因為這些類方法內部就調用了CATransaction的+begin, +commit方法
[UIView animateWithDuration:0.3 animations:^{
    layer.opacity = 0.0;
}];

當runloop開始一次新的循環時, Core Animation就會開啟一個事務, 在本次循環中的所有動畫操作, 包括我們顯示創建的事務都會被嵌套其中, 直至本次runloop循環結束之時, 再一塊提交進行動畫. 比如下方代碼:

// layerA、layerB、layerC均為寄宿/單獨圖層(hosted layer/standalone layer)

// --- runloop 新的循環開始 ---
[CATransaction begin]; // Core Animation 開始的新事務

// -- 本次循環中, 我們涉及的動畫操作 start -- 

// 修改單獨圖層屬性, 將以最外層CATransaction的動畫參數發起隱式動畫, 動畫時間為當前所在事務的動畫時間, 即默認的0.25秒
layerA.backgroundColor = [UIColor redColor].CGColor; 

// 創建顯式事務, 嵌套事務
[CATransaction begin];
[CATransaction setValue:@(1) forKey:kCATransactionAnimationDuration];
// 當前事務中的隱式動畫, 執行時間為1秒
layerB.position = CGPoint(110, 119);
[CATransaction commit];

// 實際也相當于嵌套事務
[UIView animateWithDuration:0.5 animations:^{
    layerC.opacity = 0.0;
}];

// -- 本次循環中, 我們涉及的動畫操作 end -- 

[CATransaction commit]; // Core Animation 提交所有事務
// --- runloop 循環結束 ---

利用事務, 禁止動畫發生:

[CATransaction begin];
// 設置 kCATransactionDisableActions 為 false
[CATransaction setValue:@(false)
                 forKey:kCATransactionDisableActions];

// UIView Animation顯示動畫失效
[UIView animateWithDuration:0.33
                 animations:^{
                    // 單獨的圖層layerA
                    layerA.backgroundColor = [UIColor redColor].CGColor;
                 }];

// 單獨的圖層layerB, 隱式動畫失效
layerB.opacity = 0.0;

[CATransaction commit];

五、推薦資料

本文提及內容僅為Core Animation的冰山一角, 如果想深入了解iOS動畫, 可以嘗試閱讀一下下方資料, 定會有一番收獲!

Core Animation Guide
iOS Core Animation
CATransaction In Depth

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容