淺談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官方指南中, 并沒有找到隱式動畫的明確定義, 僅提及了以下相關內容:
- 直接更改圖層CALayer的屬性就會觸發隱式動畫, 但是修改UIView對象支持圖層(backing layer)的動畫屬性是不會發生隱式動畫的, 因為UIView默認禁止了backing layer的隱式動畫, 所以對backing layer屬性的修改在UIView上的反應是直接變化, 沒有平滑過度的動畫效果
- 隱式動畫會使用當前動畫事務的參數默認值來執行動畫
- 隱式動畫會直接更改了layer模型中的值, 而顯式動畫不會(在動畫執行完后, 會根據layer模型的屬性進行"還原", 所以我們在添加動畫后需要手動修改layer的屬性來確保動畫完成時, 圖層能夠"還原"到動畫結束時的位置! 只有presentationLayer中的值會隨著動畫執行不斷改變.)
- 隱式動畫執行過程中無法直接被移除, 而顯式動畫可以通過實例方法removeAnimationForKey:或removeAllAnimations來直接移除
- 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