Core Animation的學習記錄

層次

Core Animation是不會修改CALayer的屬性的,它維護了三個平行的layer層次結構: model tree(模型層樹)、presentation tree(表示層樹)、rendering tree(渲染樹).模型層反應了我們能直接看到的layer的狀態(tài),表示層是動畫正在表現的狀態(tài)的近似值,渲染層對Core Animation是私有的。

隱式動畫

當我們修改CALayer的那些Animatable的屬性時,會產生一個隱式動畫,這個動畫是從- (nullable id<CAAction>)actionForKey:(NSString *)event方法中返回的,一般會是一個CAAnimation對象。
是的,CAAnimation遵守了CAAction協議。

這個方法的查找順序是:

  1. 調用代理(如果有)的-actionForLayer:forKey:方法
  2. layeractions字典中查找
  3. style字典中查找actions字典來獲取
  4. 調用+defaultActionForKey:來獲取

查找返回的結果:

  1. 返回NSNull, 停止查找
  2. 返回nil,繼續(xù)往下查找
  3. 返回一個CAAnimation對象,來執(zhí)行動畫

NSNull也遵守CAAction協議:

// CALayer.h中
/** NSNull protocol conformance. **/
@interface NSNull (CAActionAdditions) <CAAction>
@end

如果我們自定義一個Layer,也可讓自己定義的屬性在被更改時產生隱式動畫,只要重寫- (nullable id<CAAction>)actionForKey:(NSString *)event或者+defaultActionForKey:,返回一個適當的CAAnimatin即可。

另外,隱式動畫只存在于單獨的Layer (stand alone layer),對于依附于UIViewLayer (backing layer),可動畫屬性的改變不會造成隱式動畫。
這是因為UIViewCALayer的代理,當layer向代理詢問是否有id<CAAction>時,UIView總是返回null。

- (nullable id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
    return [NSNull null];
}

保持動畫后狀態(tài)

保持動畫完成后狀態(tài)的兩種方法:
一:設置removedOnCompletionNO,并設置fillModekCAFillModeForward。
二:在addAnimation: 前將layer的屬性置為動畫后的屬性。

方法二性能優(yōu)于方法一,如果將已完成的動畫保持在 layer 上時,會造成額外的開銷,因為渲染器會去進行額外的繪畫工作。

對于backing layer,在添加動畫的前或后,將屬性修改到最終狀態(tài)都可以。

而對于stand alone layer,不能在添加顯式動畫后修改,因為修改屬性會帶來隱式動畫,這樣就會同時存在兩個動畫,在動畫的渲染時,同一個keyPath對應多個動畫,后添加的動畫(隱式動畫)值會覆蓋前者,會造成動畫不自然。

var myAnimation = CABasicAnimation(keyPath: "position.x")
myAnimation.fromValue = oldValue 
myAnimation.toValue = newValue
myAnimation.duration  = 1.0

// set layer's position to newValue before add explicit animations
layer.position.x = newValue.floatValue // now there is a new model value

layer.addAnimation(myAnimation, forKey: "move along X")

在添加動畫前和添加動畫后更新layer屬性值的對比:

before-vs-after.gif

具體可以閱讀:Multiple Animations。

我們也可以在更新屬性值時,使用CATransaction來禁用隱式動畫,達到和backing layer一樣的效果。

var myAnimation = CABasicAnimation(keyPath: "position.x")
myAnimation.fromValue = oldValue // still the current model value
// if you want to be explicit about the toValue you can set it here
myAnimation.duration  = 1.0

CATransaction.begin()
CATransaction.setDisableActions(true)
myLayer.position.x = newValue // now there is a new model value
CATransaction.commit()

myLayer.addAnimation(myAnimation, forKey: "move along X")

動畫的重用

addAnimation:layer 會拷貝一份動畫對象,之后動畫對象再發(fā)生改變,將不會影響原圖層的動畫。

[firstLayer addAnimation: animation];
animation.beginTime = CACurrentMediaTime() + 0.5;
[secondLayer addAnimation: animation];

對動畫開始時間的修改將只影響第二個圖層,這可以讓我們有效地重用動畫。

動畫的 additive 屬性為YES 時,動畫中屬性的值會疊加到modelLayer 上來產生presentationLayer ,也就是在原圖層屬性的基礎上加上動畫的屬性值,這樣動畫中可以不必考慮原圖層的具體屬性值。
例如一個左右移動的關鍵幀動畫:

CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position.x";
animation.values = @[ @0, @10, @-10, @10, @0 ];
animation.duration = 0.4;
animation.additive = YES;
[layer addAnimation:animation forKey:@"shake"];

動畫中的屬性值,不需要帶入layer 的屬性去計算最左、最右和中間點的坐標,而是直接疊加在layer 的屬性上。這樣也有利于動畫的重用。

rotationMode

使用path 的關鍵幀動畫,可以設置rotationModekCAAnimationRotateAuto 讓視圖根據角度旋轉。

rotationModekCAAnimationRotateAuto

rotationModenil

additive

一個鍵路徑(key path)上有多個動畫時,經常會讓整體動畫變得詭異莫測。因為在動畫的渲染過程中,同一的鍵路徑上添加的動畫,后者的值會覆蓋前者的。
而當這個動畫的addtive屬性為YES時,情況會有所改變,與上面說到的動畫值的覆蓋不同,一個addtive的動畫,會將值在原動畫的基礎上疊加,layermodel也會及時更新到新的值。

在一個動畫的執(zhí)行過程中,我們可以在任意時間點加一個addtive的動畫來改變整體的動畫。這可以讓我們的動畫更加動態(tài)化。
比如在一個視圖動畫的過程中產生了用戶交互,我們可以在交互發(fā)生時再增加一個addtive的動畫來反饋出交互的效果。

多個addtive動畫可以在同一鍵路徑上,實現一些比較復雜的效果,比如下面的動畫。

A repeating animation of one path (a circle) looping while following another path (a heart)

這樣的效果如果使用其他方式實現,可能會比較復雜。而使用addtive動畫,則可以輕松實現。

let followHeartShape = CAKeyframeAnimation(keyPath: "position")
followHeartShape.additive = true
followHeartShape.path     = heartPath
followHeartShape.duration = 5
followHeartShape.repeatCount     = HUGE
followHeartShape.calculationMode = "paced"

let circleAround = CAKeyframeAnimation(keyPath: "position")
circleAround.additive = true
circleAround.path     = circlePath
circleAround.duration = 0.275
circleAround.repeatCount     = HUGE
circleAround.calculationMode = "paced"

layer.addAnimation(followHeartShape, forKey: "follow a heart shape")
layer.addAnimation(circleAround,     forKey: "loop around")

CAMediaTimingFunction

CAMediaTimingFunction可以用來控制動畫的節(jié)奏。
在代碼中我們常使用[CAMediaTimingFunction functionWithName:]方法來使用一些系統封裝好的CAMediaTimingFunction實例。其中有:

  1. linear:
  • kCAMediaTimingFunctionLinear
  1. easing:
  • kCAMediaTimingFunctionEaseIn
  • kCAMediaTimingFunctionEaseOut
  • kCAMediaTimingFunctionEaseInEaseOut
  • kCAMediaTimingFunctionDefault

也有一些第三方庫(如RBBAnimation)提供了其他的easing效果方便使用。

此外, 我們還可以使用[CAMediaTimingFunction functionWithControlPoints:x1:y1:x2:y2]來創(chuàng)建自定義的timimgFunction,創(chuàng)建出來的函數是以貝塞爾曲線為模型,參數中提供兩個控制點的坐標。但是這樣創(chuàng)建出來的函數很難形象地看出曲線長什么樣子,可以通過CAMediaTimingFunction playground網站,輸出控制點得到最終的貝塞爾曲線:

或者使用他們提供的Mac AppTween-o-Matic, 也有類似的功能,還提供對應的Demo動畫效果:

自定義緩沖函數

如果想要自定義緩沖函數對CAMediaTimingFunction進行豐富或實現它不能實現的效果,可以參考自定義緩沖函數。一些第三方也是以按其中的方法進行封裝的。

參考資料:
Obj中國
卷首語
動畫解釋
Multiple Animations

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容