前言
在iOS中,普通的動(dòng)畫可以使用UIKit提供的方法來(lái)實(shí)現(xiàn)動(dòng)畫,但如果想要實(shí)現(xiàn)復(fù)雜的動(dòng)畫效果,使用CoreAnimation框架提供的動(dòng)畫效果是最好的選擇。那么兩種動(dòng)畫方案相比之下,后者存在的主要好處包括不僅下面這些:
- 輕量級(jí)的數(shù)據(jù)結(jié)構(gòu),可以同時(shí)讓上百個(gè)圖層產(chǎn)生動(dòng)畫效果
- 擁有獨(dú)立的線程用于執(zhí)行我們的動(dòng)畫接口
- 完成動(dòng)畫配置后,核心動(dòng)畫會(huì)代替我們完全控制完成對(duì)應(yīng)的動(dòng)畫幀
- 提高應(yīng)用性能。只有在發(fā)生改變的時(shí)候才重繪內(nèi)容,消除了動(dòng)畫的幀速率上的運(yùn)行代碼

在
CoreAnimation框架下,最主要的兩個(gè)部分是圖層CALayer以及動(dòng)畫CAAnimation類。前者管理著一個(gè)可以被用來(lái)實(shí)現(xiàn)動(dòng)畫的位圖上下文;后者是一個(gè)抽象的動(dòng)畫基類,它提供了對(duì)CAMediaTiming和CAAction協(xié)議的支持,方便子類實(shí)例直接作用于CALayer本身來(lái)實(shí)現(xiàn)動(dòng)畫效果。接下來(lái)筆者會(huì)分段分別講述上面提到的類,參考信息來(lái)自于蘋果官方文檔以及objc中國(guó)
CALayer
CALayer類結(jié)構(gòu)
如果你喜歡動(dòng)畫效果,在網(wǎng)上開源的動(dòng)畫實(shí)現(xiàn)中總是能看到CALayer及其子類的應(yīng)用,那么了解這個(gè)圖層類別先從它的結(jié)構(gòu)看起(此處列出了了部分屬性并且去除了注釋):
public class CALayer : NSObject, NSCoding, CAMediaTiming {
public func presentationLayer() -> AnyObject?
public func modelLayer() -> AnyObject
public var bounds: CGRect
public var position: CGPoint
public var anchorPoint: CGPoint
public var transform: CATransform3D
public var frame: CGRect
public var hidden: Bool
public var superlayer: CALayer? { get }
public func removeFromSuperlayer()
public func addSublayer(layer: CALayer)
public func insertSublayer(layer: CALayer, below sibling: CALayer?)
public func insertSublayer(layer: CALayer, above sibling: CALayer?)
public func replaceSublayer(layer: CALayer, with layer2: CALayer)
public var sublayerTransform: CATransform3D
public var mask: CALayer?
public var masksToBounds: Bool
public func hitTest(p: CGPoint) -> CALayer?
public func containsPoint(p: CGPoint) -> Bool
public var shadowColor: CGColor?
public var shadowOpacity: Float
public var shadowOffset: CGSize
public var shadowRadius: CGFloat
public var contents: AnyObject?
public var contentsRect: CGRect
public var cornerRadius: CGFloat
public var borderWidth: CGFloat
public var borderColor: CGColor?
public var opacity: Float
}
根據(jù)CALayer Class Reference中的描述,在每一個(gè)UIView的背后都有一個(gè)CALayer對(duì)象用來(lái)協(xié)助它顯示內(nèi)容,它自身管理著我們提供給視圖顯示的位圖上下文以及保存這些位圖上下文的幾何信息。通過上面的代碼可以看出:
CALayer是NSObject的子類而非UIResponder的子類,因此圖層本身無(wú)法響應(yīng)用戶操作事件卻擁有著事件響應(yīng)鏈相似的判斷方法,所以CALayer需要包裝成一個(gè)UIView容器來(lái)完成這一功能。每一個(gè)
UIView自身存在一個(gè)CALayer來(lái)顯示內(nèi)容。在后者的屬性中我們可以看到存在著多個(gè)和UIView界面屬性對(duì)應(yīng)的變量,因此我們?cè)谛薷?code>UIView的界面屬性的時(shí)候其實(shí)是修改了這個(gè)UIView對(duì)應(yīng)的layer的屬性。CALayer擁有和UIView一樣的樹狀層級(jí)關(guān)系,也有類似UIView添加子視圖的addSublayer這些類似的方法。CALayer可以獨(dú)立于UIView之外顯示在屏幕上,但我們需要重寫事件方法來(lái)完成對(duì)它的響應(yīng)操作
對(duì)于蘋果為什么要把UIView和CALayer區(qū)分開來(lái),網(wǎng)上已經(jīng)有了一篇很詳(qi)細(xì)(pa)的文章講解這個(gè)問題都有了CALayer,為什么還要UIView
圖層樹和隱式動(dòng)畫
在每一個(gè)CALayer中,都有三個(gè)重要的層次樹,它們負(fù)責(zé)相互協(xié)調(diào)完成圖層的渲染展示效果。這三個(gè)層次樹分別是:
- 模型樹。通過
layer.modelLayer獲取,當(dāng)我們修改CALayer的可動(dòng)畫屬性時(shí),模型樹對(duì)應(yīng)的屬性就會(huì)立刻被修改成對(duì)應(yīng)的數(shù)值 - 呈現(xiàn)樹。通過
layer. presentationLayer獲取,呈現(xiàn)樹保存著當(dāng)前圖層狀態(tài)的顯示數(shù)據(jù),即會(huì)隨著動(dòng)畫的過程不斷更新圖層的狀態(tài)數(shù)據(jù) - 渲染樹,iOS并沒有提供任何API來(lái)獲取這一個(gè)層次樹。顧名思義,它通過結(jié)合
modelLayer跟presentationLayer中設(shè)置的效果來(lái)將內(nèi)容渲染到屏幕上
CALayer中的顯示數(shù)據(jù)幾乎都是可動(dòng)畫屬性,這個(gè)特性為我們制作核心動(dòng)畫提供了很大的實(shí)踐基礎(chǔ)。在一個(gè)單獨(dú)的CALayer中(也就是說(shuō)這個(gè)layer并沒有和任何UIView綁定),我們修改它的顯示屬性的時(shí)候,都會(huì)觸發(fā)一個(gè)從舊值到新值之間的簡(jiǎn)單動(dòng)畫效果,這種動(dòng)畫我們稱之為隱式動(dòng)畫:
class ViewController: UIViewController {
let layer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
layer.strokeEnd = 0
layer.lineWidth = 6
layer.fillColor = UIColor.clearColor().CGColor
layer.strokeColor = UIColor.redColor().CGColor
self.view.layer.addSublayer(layer)
}
@IBAction func actionToAnimate() {
layer.path = UIBezierPath(arcCenter: self.view.center, radius: 100, startAngle: 0, endAngle: 2*CGFloat(M_PI), clockwise: true).CGPath
layer.strokeEnd = 1
}
}
可以看到上面的代碼中我單獨(dú)創(chuàng)建了一個(gè)CALayer并且將它添加到當(dāng)前的控制器視圖的圖層上,strokeEnd這一屬性表示填充百分比。當(dāng)這個(gè)屬性發(fā)生變化的時(shí)候,產(chǎn)生了一個(gè)畫圈的動(dòng)畫效果:

在隱式動(dòng)畫的實(shí)現(xiàn)背后,隱藏著一個(gè)最重要的扮演角色
CAAction協(xié)議這一過程會(huì)在下面進(jìn)行詳細(xì)的介紹。那么在上面這個(gè)隱式動(dòng)畫的過程中,模型樹和呈現(xiàn)樹發(fā)生了哪些變化呢?由于系統(tǒng)的動(dòng)畫時(shí)長(zhǎng)默認(rèn)為0.25秒,我設(shè)置一個(gè)0.05秒的定時(shí)器在每次回調(diào)的時(shí)候查看一下這兩個(gè)層次樹的信息:
@IBAction func actionToAnimate() {
timer = NSTimer(timeInterval: 0.05, target: self, selector: #selector(timerCallback), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timer!, forMode: NSRunLoopCommonModes)
layer.path = UIBezierPath(arcCenter: self.view.center, radius: 100, startAngle: 0, endAngle: 2*CGFloat(M_PI), clockwise: true).CGPath
layer.strokeEnd = 1
}
@objc private func timerCallback() {
print("========================\nmodelLayer: \t\(layer.modelLayer().strokeEnd)\ntpresentationLayer: \t\(layer.presentationLayer()!.strokeEnd)")
if fabs((layer.presentationLayer()?.strokeEnd)! - 1) < 0.01 {
if let _ = timer {
timer?.invalidate()
timer = nil
}
}
}
控制臺(tái)的輸出結(jié)果如下:
========================
modelLayer: 1.0
presentationLayer: 0.294064253568649
========================
modelLayer: 1.0
presentationLayer: 0.676515340805054
========================
modelLayer: 1.0
presentationLayer: 0.883405208587646
========================
modelLayer: 1.0
presentationLayer: 0.974191427230835
========================
modelLayer: 1.0
presentationLayer: 0.999998211860657
可以看到當(dāng)一個(gè)隱式動(dòng)畫發(fā)生的時(shí)候,modelLayer的屬性被修改成動(dòng)畫最終的結(jié)果值。而系統(tǒng)會(huì)根據(jù)動(dòng)畫時(shí)長(zhǎng)和最終效果值計(jì)算出動(dòng)畫中每一幀的數(shù)值,然后依次更新設(shè)置到presentationLayer當(dāng)中。最終這些計(jì)算工作都完成之后,渲染樹renderingTree根據(jù)這些值將動(dòng)畫效果渲染到屏幕上。
那么通過層次樹我們能制作什么呢?假設(shè)我需要制作下面這么一個(gè)粘性的彈球動(dòng)畫,那么我在界面的最左側(cè)、最右側(cè)以及中間各自添加了一個(gè)CALayer,當(dāng)點(diǎn)擊按鈕的時(shí)候給左右兩側(cè)的layer添加一個(gè)勻速的position下移動(dòng)畫,中間的centerLayer添加一個(gè)彈簧動(dòng)畫。通過使用定時(shí)器更新獲取這三個(gè)layer的呈現(xiàn)樹y軸坐標(biāo)來(lái)繪制區(qū)域形成這樣一個(gè)動(dòng)畫:

其他屬性
除了上面重點(diǎn)介紹的屬性之外,下面的屬性我只進(jìn)行簡(jiǎn)單的介紹,詳細(xì)的使用以及動(dòng)畫作用會(huì)在以后對(duì)應(yīng)使用的動(dòng)畫中更詳細(xì)的講解:
position和anchorPoint
anchorPoint是一個(gè)x和y值取值范圍內(nèi)在0~1之間CGPoint類型,它決定了當(dāng)圖層發(fā)生幾何仿射變換時(shí)基于的坐標(biāo)原點(diǎn)。默認(rèn)情況下為0.5, 0.5,由anchorPoint和frame經(jīng)過計(jì)算獲得圖層的position這個(gè)值。更多介紹這兩個(gè)屬性的文章在徹底理解position和anchorPointmask和maskToBounds
maskToBounds值為true時(shí)表示超出圖層范圍外的所有子圖層都不會(huì)進(jìn)行渲染,當(dāng)我們?cè)O(shè)置UIView的clipsToBounds時(shí)實(shí)際上就是在修改maskToBounds這個(gè)屬性。mask這個(gè)屬性表示一個(gè)遮罩圖層,在這個(gè)遮罩之外的內(nèi)容不予渲染顯示,在上一篇?jiǎng)赢?a href="http://www.itdecent.cn/p/e189696dd535" target="_blank">碎片動(dòng)畫中使用的maskView實(shí)際上也是修改這個(gè)屬性cornerRadius、borderWidth和borderColor
borderWidth和borderColor設(shè)置了圖層的邊緣線條的顏色以及寬度,正常情況下這兩個(gè)屬性在layer的層次上不怎么使用。后者cornerRadius設(shè)置圓角半徑,這個(gè)半徑會(huì)影響邊緣線條的形狀-
shadowColor、shadowOpacity、shadowOffset和shadowRadius
這四個(gè)屬性結(jié)合起來(lái)可以制作陰影效果。shadowOpacity默認(rèn)情況下值為0,這意味著即便你設(shè)置了其他三個(gè)屬性,只要不修改這個(gè)值,你的陰影效果就是透明的。其次,不要糾結(jié)shadowOffset這個(gè)決定陰影效果位置偏移的屬性為什么會(huì)是CGSize而不是CGPoint。我通過下面這段代碼設(shè)置的陰影效果如下:layer.shadowColor = UIColor.grayColor().CGColor layer.shadowOffset = CGSize(width: 2, height: 5) layer.shadowOpacity = 1圖層陰影效果 其他屬性
這里包括了transform的仿射變換屬性,相比UIView的同名屬性,它可以設(shè)置z軸上的值實(shí)現(xiàn)更多的幾何變換效果。此外還有bound和frame這些影響圖層顯示范圍的屬性,就不再多說(shuō)
CAAnimation
CAAnimation的子類
開頭說(shuō)過,CAAnimation是一個(gè)封裝出來(lái)的基類,其最重要的目的在于遵循兩個(gè)重要的動(dòng)畫相關(guān)協(xié)議,所以解析動(dòng)畫類型要從它的子類依賴關(guān)系下手。在蘋果文檔中,CAAnimation的直接子類包括這些:

從圖中我們可以看到存在這么三個(gè)子類:
CAAnimationGroup動(dòng)畫組對(duì)象,其作用是將多個(gè)CAAnimation動(dòng)畫實(shí)例組合在一起,讓圖層同時(shí)執(zhí)行多個(gè)動(dòng)畫效果。在本文中不會(huì)進(jìn)行更多的介紹CAPropertyAnimation屬性動(dòng)畫,這是很多核心動(dòng)畫類的父類,同時(shí)也是一個(gè)抽象的CAAnimation子類(這兩父子都是抽象主義)他提供了對(duì)圖層關(guān)鍵路徑的屬性進(jìn)行動(dòng)畫的重要功能,在其基礎(chǔ)上衍生的眾多子類是實(shí)現(xiàn)動(dòng)畫的重要工具CATransition過度動(dòng)畫類,不得不說(shuō)在現(xiàn)今這個(gè)版本這個(gè)類的定位非常尷尬。在CATransform3D以及自定義轉(zhuǎn)場(chǎng)API大行其道的這個(gè)年代,它提供的作用實(shí)在太輕微了。另一方面它還可能因?yàn)?code>私有api的問題導(dǎo)致應(yīng)用的上架失敗,不過了解這個(gè)類也是可以的,在這篇CATransition用法中可以學(xué)習(xí)如何使用CATransition制作動(dòng)畫
類結(jié)構(gòu)屬性
從上面的圖中我們可以看到CAAnimation遵循了兩個(gè)協(xié)議,在其本身屬性中并沒有太多的屬性。其中大部分的動(dòng)畫相關(guān)屬性都是在協(xié)議中聲明的,在實(shí)現(xiàn)中動(dòng)態(tài)生成了setter和getter
public class CAAnimation : NSObject, NSCoding, NSCopying, CAMediaTiming, CAAction {
public class func defaultValueForKey(key: String) -> AnyObject?
public func shouldArchiveValueForKey(key: String) -> Bool
public var timingFunction: CAMediaTimingFunction?
public var delegate: AnyObject?
public var removedOnCompletion: Bool
}
通過CAAnimation的類結(jié)構(gòu),可以分為屬性和方法兩個(gè)部分,其中屬性是我們需要重點(diǎn)關(guān)注的
defaultValueForKey和shouldArchiveValueForKey
這兩個(gè)方法從名字上看就知道跟NSCoding協(xié)議脫不開干系,后者通過傳入一個(gè)關(guān)鍵字對(duì)動(dòng)畫對(duì)象進(jìn)行序列化本地存儲(chǔ),并且返回成功與否。然后使用相同的關(guān)鍵字調(diào)用前者來(lái)獲取這個(gè)持久化的動(dòng)畫對(duì)象-
timingFunction
這個(gè)是個(gè)有趣的屬性,決定了動(dòng)畫的視覺效果。我在從UIView動(dòng)畫說(shuō)起中提到過動(dòng)畫的視覺效果,包括淡入、淡出等效果,這些效果用字符串表示:public let kCAMediaTimingFunctionLinear: String public let kCAMediaTimingFunctionEaseIn: String public let kCAMediaTimingFunctionEaseOut: String public let kCAMediaTimingFunctionEaseInEaseOut: String public let kCAMediaTimingFunctionDefault: String let timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)動(dòng)畫時(shí)間曲線 delegate
在NSObject中實(shí)現(xiàn)了CAAnimation的回調(diào)方法,包括不限于animationDidStart和animationDidStop等方法。因此在iOS中的任何一個(gè)對(duì)象都能成為CAAnimation的代理人。通過這個(gè)代理我們可以在動(dòng)畫結(jié)束時(shí)移除動(dòng)畫效果等操作removedOnCompletion
決定了動(dòng)畫結(jié)束之后是否將動(dòng)畫從相應(yīng)的圖層上移除,默認(rèn)是true。由于圖層動(dòng)畫實(shí)際上相當(dāng)于障眼法(使用CAAnimaiton實(shí)現(xiàn)動(dòng)畫效果的時(shí)候并不會(huì)真的修改對(duì)應(yīng)的屬性),即是在動(dòng)畫結(jié)束時(shí)圖層會(huì)回到動(dòng)畫開始的狀態(tài)。通過設(shè)置這個(gè)值為false以及其他配置,可以避免這種事情發(fā)生
動(dòng)畫協(xié)議屬性
除了CAAnimation本身的屬性之外,另外兩個(gè)協(xié)議中聲明了決定動(dòng)畫時(shí)長(zhǎng)、前后動(dòng)畫效果等關(guān)鍵屬性:
public protocol CAMediaTiming {
public var beginTime: CFTimeInterval { get set }
public var duration: CFTimeInterval { get set }
public var speed: Float { get set }
public var timeOffset: CFTimeInterval { get set }
public var repeatCount: Float { get set }
public var repeatDuration: CFTimeInterval { get set }
public var autoreverses: Bool { get set }
public var fillMode: String { get set }
}
CAMediaTiming是一個(gè)控制動(dòng)畫時(shí)間的協(xié)議,提供了動(dòng)畫過程中的時(shí)間相關(guān)的屬性,對(duì)于這些屬性在控制動(dòng)畫時(shí)間一文中講解的非常清楚了,筆者在這里就不再一一介紹。此外,還有另一個(gè)協(xié)議CAAction協(xié)議:
public protocol CAAction {
/* Called to trigger the event named 'path' on the receiver. The object
* (e.g. the layer) on which the event happened is 'anObject'. The
* arguments dictionary may be nil, if non-nil it carries parameters
* associated with the event. */
@available(iOS 2.0, *)
public func runActionForKey(event: String, object anObject: AnyObject, arguments dict: [NSObject : AnyObject]?)
}
在動(dòng)畫發(fā)生之后這個(gè)方法會(huì)被調(diào)用,這個(gè)方法把將要發(fā)生的事件告訴圖層,從而讓圖層做出對(duì)應(yīng)的操作,比如渲染等。
CAAction
顯式動(dòng)畫
開頭筆者提到過隱式動(dòng)畫是單獨(dú)的layer的可動(dòng)畫屬性發(fā)生改變時(shí)自動(dòng)產(chǎn)生的過度動(dòng)畫,那么肯定就有對(duì)應(yīng)的顯式動(dòng)畫。顯式動(dòng)畫的制作過程是創(chuàng)建一個(gè)動(dòng)畫對(duì)象,然后添加到實(shí)現(xiàn)動(dòng)畫的圖層上。下面這段代碼創(chuàng)建了一個(gè)修改layer.position.y的基礎(chǔ)動(dòng)畫對(duì)象,然后設(shè)置動(dòng)畫結(jié)束值為160后添加到layer層上,動(dòng)畫的默認(rèn)時(shí)長(zhǎng)是0.25秒:
let animation = CABasicAnimation(keyPath: "position.y")
animation.toValue = NSNumber(float: 160)
layer.position.y = 160
layer.addAnimation(animation, forKey: nil)
對(duì)于動(dòng)畫的更詳細(xì)講解不在本篇文章的計(jì)劃內(nèi),在接下來(lái)的核心動(dòng)畫中筆者會(huì)更加詳細(xì)的介紹各式各樣的CAAnimation子類以用于不同的動(dòng)畫場(chǎng)景。這里放上上面粘性彈窗的核心代碼:
func startAnimation() {
let displayLink = CADisplayLink(target: self, selector: #selector(fluctAnimation(_:)))
displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
let move = CABasicAnimation(keyPath: "position.y")
move.toValue = NSNumber(float: 160)
leftLayer.position.y = 160
rightLayer.position.y = 160
leftLayer.addAnimation(move, forKey: nil)
rightLayer.addAnimation(move, forKey: nil)
let spring = CASpringAnimation(keyPath: "position.y")
spring.damping = 15
spring.initialVelocity = 40
spring.toValue = NSNumber(float: 160)
centerLayer.position.y = 160
centerLayer.addAnimation(spring, forKey: "spring")
}
給三個(gè)圖層添加了下移的動(dòng)畫之后,創(chuàng)建CADisplayLink定時(shí)器來(lái)同步屏幕刷新頻率更新彈出效果:
@objc private func fluctAnimation(link: CADisplayLink) {
let path = UIBezierPath()
path.moveToPoint(CGPointZero)
guard let _ = centerLayer.animationForKey("spring") else {
return
}
let offset = leftLayer.presentationLayer()!.position.y - centerLayer.presentationLayer()!.position.y
var controlY: CGFloat = 160
if offset < 0 {
controlY = centerLayer.presentationLayer()!.position.y + 30
} else if offset > 0 {
controlY = centerLayer.presentationLayer()!.position.y - 30
}
path.addLineToPoint(leftLayer.presentationLayer()!.position)
path.addQuadCurveToPoint(rightLayer.presentationLayer()!.position, controlPoint: CGPoint(x: centerLayer.position.x, y: controlY))
path.addLineToPoint(CGPoint(x: UIScreen.mainScreen().bounds.width, y: 0))
path.closePath()
fluctLayer.path = path.CGPath
}
隱式動(dòng)畫發(fā)生了什么
上面說(shuō)過隱式動(dòng)畫發(fā)生在單獨(dú)的CALayer對(duì)象的可動(dòng)畫屬性被改變時(shí)。如果我們這個(gè)layer已經(jīng)存在與之綁定的UIView對(duì)象,那么當(dāng)我們直接修改這個(gè)layer的屬性的時(shí)候,只會(huì)瞬間從舊值變成新值的顯示效果,不會(huì)有額外的效果。在CoreAnimation的編程指南中對(duì)此做出了解釋:UIView默認(rèn)情況下禁止了layer動(dòng)畫,但在animate block中重新啟用了它們。這是我們看到的行為,但如果認(rèn)真去挖掘這一機(jī)制的內(nèi)部實(shí)現(xiàn),我們會(huì)驚訝于view和layer之間協(xié)同工作的精心設(shè)計(jì),這里就要提到CAAction

當(dāng)任何一個(gè)可動(dòng)畫的
layer的屬性發(fā)生改變的時(shí)候,layer通過向它的代理人發(fā)送actionForLayer(layer:event:)方法來(lái)查詢一個(gè)對(duì)應(yīng)屬性變化的CAAction對(duì)象,這個(gè)方法可以返回下面三個(gè)結(jié)果:
- 返回一個(gè)遵循
CAAction的對(duì)象,這種情況下layer使用這個(gè)動(dòng)作完成動(dòng)畫 - 返回一個(gè)
nil,這樣layer就會(huì)到其他地方繼續(xù)尋找 - 返回一個(gè)
NSNull對(duì)象,這樣layer就會(huì)停止查找并且不執(zhí)行動(dòng)畫
正常來(lái)說(shuō),當(dāng)一個(gè)CALayer和UIView關(guān)聯(lián)的時(shí)候,這個(gè)UIView對(duì)象會(huì)成為layer的代理人。因此從返回值上來(lái)說(shuō),當(dāng)layer的屬性被我們修改的時(shí)候,這個(gè)關(guān)聯(lián)的UIView對(duì)象一般都是直接返回NSNull對(duì)象,而只有在animate block的狀態(tài)下才會(huì)返回實(shí)際的動(dòng)畫效果,方便讓圖層繼續(xù)查找處理動(dòng)作的方案。我們通過代碼來(lái)驗(yàn)證:
print("===========normal call===========")
print("\(self.view.actionForLayer(self.view.layer, forKey: "opacity"))")
UIView.animateWithDuration(0.25) {
print("===========animate block call===========")
print("\(self.view.actionForLayer(self.view.layer, forKey: "opacity"))")
}
控制臺(tái)輸出結(jié)果如下,在動(dòng)畫block中確實(shí)返回了一個(gè)CABasicAnimation對(duì)象來(lái)協(xié)同完成這個(gè)動(dòng)畫效果
===========normal call===========
Optional(<null>)
===========animate block call===========
Optional(<CABasicAnimation:0x7f8712d30c90; delegate = <UIViewAnimationState: 0x7f8712d304d0>; fillMode = both; timingFunction = easeInEaseOut; duration = 0.25; fromValue = 1; keyPath = opacity>)
通常來(lái)說(shuō)處在動(dòng)畫代碼塊中的UIView都會(huì)返回這么一個(gè)核心動(dòng)畫對(duì)象,但如果返回的是nil,圖層還有繼續(xù)查找其他的動(dòng)作解決方案,整個(gè)的查找過程共有四次,這個(gè)在CALayer的頭文件中已經(jīng)說(shuō)明了:

當(dāng)
layer對(duì)象查找到了屬性修改動(dòng)作的動(dòng)畫時(shí),就會(huì)調(diào)用addAnimation(_:forKey:)方法開始執(zhí)行動(dòng)畫效果。同樣的,我們繼承CALayer對(duì)象來(lái)重寫這個(gè)方法:
class LXDActionLayer: CALayer {
override func addAnimation(anim: CAAnimation, forKey key: String?) {
print("***********************************************")
print("Layer will add an animation: \(anim)")
super.addAnimation(anim, forKey: key)
}
}
class LXDActionView: UIView {
override class func layerClass() -> AnyClass {
return LXDActionLayer.classForCoder()
}
}
override func viewDidLoad() {
super.viewDidLoad()
let actionView = LXDActionView()
view.addSubview(actionView)
print("===========normal call===========")
print("\(self.view.actionForLayer(self.view.layer, forKey: "opacity"))")
actionView.layer.opacity = 0.5
UIView.animateWithDuration(0.25) {
print("===========animate block call===========")
print("\(self.view.actionForLayer(self.view.layer, forKey: "opacity"))")
actionView.layer.opacity = 0
}
控制臺(tái)輸出結(jié)果如下:
===========normal call===========
Optional(<null>)
===========animate block call===========
Optional(<CABasicAnimation:0x7f8f00eb1850; delegate = <UIViewAnimationState: 0x7f8f00eb0e90>; fillMode = both; timingFunction = easeInEaseOut; duration = 0.25; fromValue = 1; keyPath = opacity>)
***********************************************
Layer will add an animation: <CABasicAnimation: 0x7f8f00eb2400>
這里可能會(huì)有人有疑惑,為什么兩次輸出的CABasicAnimation的地址不一樣。為了保證同一個(gè)動(dòng)畫對(duì)象可以作用于多個(gè)CALayer對(duì)象執(zhí)行,在addAnimation(_:forKey:)方法調(diào)用的時(shí)候都會(huì)對(duì)CAAnimation對(duì)象進(jìn)行一次copy操作。各位可以繼承CABasicAnimation對(duì)象重寫copy方法自行測(cè)試
尾言
本來(lái)筆者想要直接使用動(dòng)畫粒子開始講解核心動(dòng)畫這一框架,但是考慮到如果能夠全面的對(duì)核心動(dòng)畫進(jìn)行一次講解,這對(duì)于以后的文章講解以及動(dòng)畫粒子的制作有很大的幫助。本文demo
上一篇:碎片動(dòng)畫
下一篇:按鈕動(dòng)畫
轉(zhuǎn)載請(qǐng)注明本文作者和轉(zhuǎn)載地址

