前言
在iOS中谚中,普通的動畫可以使用UIKit
提供的方法來實現(xiàn)動畫,但如果想要實現(xiàn)復(fù)雜的動畫效果洁桌,使用CoreAnimation
框架提供的動畫效果是最好的選擇荤牍。那么兩種動畫方案相比之下,后者存在的主要好處包括不僅下面這些:
- 輕量級的數(shù)據(jù)結(jié)構(gòu)啰脚,可以同時讓上百個圖層產(chǎn)生動畫效果
- 擁有獨立的線程用于執(zhí)行我們的動畫接口
- 完成動畫配置后殷蛇,核心動畫會代替我們完全控制完成對應(yīng)的動畫幀
- 提高應(yīng)用性能。只有在發(fā)生改變的時候才重繪內(nèi)容橄浓,消除了動畫的幀速率上的運行代碼
在
CoreAnimation
框架下粒梦,最主要的兩個部分是圖層CALayer
以及動畫CAAnimation
類。前者管理著一個可以被用來實現(xiàn)動畫的位圖上下文荸实;后者是一個抽象的動畫基類匀们,它提供了對CAMediaTiming
和CAAction
協(xié)議的支持,方便子類實例直接作用于CALayer
本身來實現(xiàn)動畫效果准给。接下來筆者會分段分別講述上面提到的類泄朴,參考信息來自于蘋果官方文檔以及objc中國
CALayer
CALayer類結(jié)構(gòu)
如果你喜歡動畫效果,在網(wǎng)上開源的動畫實現(xiàn)中總是能看到CALayer
及其子類的應(yīng)用圆存,那么了解這個圖層類別先從它的結(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中的描述叼旋,在每一個UIView
的背后都有一個CALayer
對象用來協(xié)助它顯示內(nèi)容,它自身管理著我們提供給視圖顯示的位圖上下文以及保存這些位圖上下文的幾何信息沦辙。通過上面的代碼可以看出:
CALayer
是NSObject
的子類而非UIResponder
的子類夫植,因此圖層本身無法響應(yīng)用戶操作事件卻擁有著事件響應(yīng)鏈相似的判斷方法,所以CALayer
需要包裝成一個UIView
容器來完成這一功能油讯。每一個
UIView
自身存在一個CALayer
來顯示內(nèi)容详民。在后者的屬性中我們可以看到存在著多個和UIView
界面屬性對應(yīng)的變量,因此我們在修改UIView
的界面屬性的時候其實是修改了這個UIView
對應(yīng)的layer
的屬性陌兑。CALayer
擁有和UIView
一樣的樹狀層級關(guān)系沈跨,也有類似UIView
添加子視圖的addSublayer
這些類似的方法。CALayer
可以獨立于UIView
之外顯示在屏幕上兔综,但我們需要重寫事件方法來完成對它的響應(yīng)操作
對于蘋果為什么要把UIView
和CALayer
區(qū)分開來饿凛,網(wǎng)上已經(jīng)有了一篇很詳(qi)細(xì)(pa)的文章講解這個問題都有了CALayer狞玛,為什么還要UIView
圖層樹和隱式動畫
在每一個CALayer
中,都有三個重要的層次樹涧窒,它們負(fù)責(zé)相互協(xié)調(diào)完成圖層的渲染展示效果心肪。這三個層次樹分別是:
- 模型樹。通過
layer.modelLayer
獲取纠吴,當(dāng)我們修改CALayer
的可動畫屬性時硬鞍,模型樹對應(yīng)的屬性就會立刻被修改成對應(yīng)的數(shù)值 - 呈現(xiàn)樹。通過
layer. presentationLayer
獲取戴已,呈現(xiàn)樹保存著當(dāng)前圖層狀態(tài)的顯示數(shù)據(jù)固该,即會隨著動畫的過程不斷更新圖層的狀態(tài)數(shù)據(jù) - 渲染樹,iOS并沒有提供任何API來獲取這一個層次樹糖儡。顧名思義伐坏,它通過結(jié)合
modelLayer
跟presentationLayer
中設(shè)置的效果來將內(nèi)容渲染到屏幕上
CALayer
中的顯示數(shù)據(jù)幾乎都是可動畫屬性,這個特性為我們制作核心動畫提供了很大的實踐基礎(chǔ)休玩。在一個單獨的CALayer
中(也就是說這個layer
并沒有和任何UIView
綁定)著淆,我們修改它的顯示屬性的時候,都會觸發(fā)一個從舊值
到新值
之間的簡單動畫效果拴疤,這種動畫我們稱之為隱式動畫:
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
}
}
可以看到上面的代碼中我單獨創(chuàng)建了一個CALayer
并且將它添加到當(dāng)前的控制器視圖的圖層上,strokeEnd
這一屬性表示填充百分比独泞。當(dāng)這個屬性發(fā)生變化的時候呐矾,產(chǎn)生了一個畫圈的動畫效果:
在隱式動畫的實現(xiàn)背后,隱藏著一個最重要的扮演角色
CAAction
協(xié)議這一過程會在下面進行詳細(xì)的介紹懦砂。那么在上面這個隱式動畫的過程中蜒犯,模型樹和呈現(xiàn)樹發(fā)生了哪些變化呢?由于系統(tǒng)的動畫時長默認(rèn)為0.25
秒荞膘,我設(shè)置一個0.05
秒的定時器在每次回調(diào)的時候查看一下這兩個層次樹的信息:
@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
}
}
}
控制臺的輸出結(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)一個隱式動畫發(fā)生的時候罚随,modelLayer
的屬性被修改成動畫最終的結(jié)果值。而系統(tǒng)會根據(jù)動畫時長和最終效果值計算出動畫中每一幀的數(shù)值羽资,然后依次更新設(shè)置到presentationLayer
當(dāng)中淘菩。最終這些計算工作都完成之后,渲染樹renderingTree
根據(jù)這些值將動畫效果渲染到屏幕上屠升。
那么通過層次樹我們能制作什么呢潮改?假設(shè)我需要制作下面這么一個粘性的彈球動畫,那么我在界面的最左側(cè)腹暖、最右側(cè)以及中間各自添加了一個CALayer
汇在,當(dāng)點擊按鈕的時候給左右兩側(cè)的layer
添加一個勻速的position
下移動畫,中間的centerLayer
添加一個彈簧動畫脏答。通過使用定時器更新獲取這三個layer
的呈現(xiàn)樹y
軸坐標(biāo)來繪制區(qū)域形成這樣一個動畫:
其他屬性
除了上面重點介紹的屬性之外糕殉,下面的屬性我只進行簡單的介紹亩鬼,詳細(xì)的使用以及動畫作用會在以后對應(yīng)使用的動畫中更詳細(xì)的講解:
position
和anchorPoint
anchorPoint
是一個x
和y
值取值范圍內(nèi)在0~1
之間CGPoint
類型,它決定了當(dāng)圖層發(fā)生幾何仿射變換時基于的坐標(biāo)原點阿蝶。默認(rèn)情況下為0.5, 0.5
雳锋,由anchorPoint
和frame
經(jīng)過計算獲得圖層的position
這個值。更多介紹這兩個屬性的文章在徹底理解position和anchorPointmask
和maskToBounds
maskToBounds
值為true
時表示超出圖層范圍外的所有子圖層都不會進行渲染赡磅,當(dāng)我們設(shè)置UIView
的clipsToBounds
時實際上就是在修改maskToBounds
這個屬性魄缚。mask
這個屬性表示一個遮罩圖層,在這個遮罩之外的內(nèi)容不予渲染顯示焚廊,在上一篇動畫碎片動畫中使用的maskView
實際上也是修改這個屬性cornerRadius
冶匹、borderWidth
和borderColor
borderWidth
和borderColor
設(shè)置了圖層的邊緣線條的顏色以及寬度,正常情況下這兩個屬性在layer
的層次上不怎么使用咆瘟。后者cornerRadius
設(shè)置圓角半徑嚼隘,這個半徑會影響邊緣線條的形狀-
shadowColor
、shadowOpacity
袒餐、shadowOffset
和shadowRadius
這四個屬性結(jié)合起來可以制作陰影效果飞蛹。shadowOpacity
默認(rèn)情況下值為0
,這意味著即便你設(shè)置了其他三個屬性灸眼,只要不修改這個值卧檐,你的陰影效果就是透明的。其次焰宣,不要糾結(jié)shadowOffset
這個決定陰影效果位置偏移的屬性為什么會是CGSize
而不是CGPoint
霉囚。我通過下面這段代碼設(shè)置的陰影效果如下:layer.shadowColor = UIColor.grayColor().CGColor layer.shadowOffset = CGSize(width: 2, height: 5) layer.shadowOpacity = 1
圖層陰影效果 其他屬性
這里包括了transform
的仿射變換屬性,相比UIView
的同名屬性匕积,它可以設(shè)置z
軸上的值實現(xiàn)更多的幾何變換效果盈罐。此外還有bound
和frame
這些影響圖層顯示范圍的屬性,就不再多說
CAAnimation
CAAnimation的子類
開頭說過闪唆,CAAnimation
是一個封裝出來的基類盅粪,其最重要的目的在于遵循兩個重要的動畫相關(guān)協(xié)議,所以解析動畫類型要從它的子類依賴關(guān)系下手悄蕾。在蘋果文檔中票顾,CAAnimation
的直接子類包括這些:
從圖中我們可以看到存在這么三個子類:
CAAnimationGroup
動畫組對象,其作用是將多個CAAnimation
動畫實例組合在一起笼吟,讓圖層同時執(zhí)行多個動畫效果库物。在本文中不會進行更多的介紹CAPropertyAnimation
屬性動畫,這是很多核心動畫類的父類贷帮,同時也是一個抽象的CAAnimation
子類(這兩父子都是抽象主義)他提供了對圖層關(guān)鍵路徑的屬性進行動畫的重要功能戚揭,在其基礎(chǔ)上衍生的眾多子類是實現(xiàn)動畫的重要工具CATransition
過度動畫類,不得不說在現(xiàn)今這個版本這個類的定位非常尷尬撵枢。在CATransform3D
以及自定義轉(zhuǎn)場API大行其道的這個年代民晒,它提供的作用實在太輕微了精居。另一方面它還可能因為私有api
的問題導(dǎo)致應(yīng)用的上架失敗,不過了解這個類也是可以的潜必,在這篇CATransition用法中可以學(xué)習(xí)如何使用CATransition
制作動畫
類結(jié)構(gòu)屬性
從上面的圖中我們可以看到CAAnimation
遵循了兩個協(xié)議靴姿,在其本身屬性中并沒有太多的屬性。其中大部分的動畫相關(guān)屬性都是在協(xié)議中聲明的磁滚,在實現(xiàn)中動態(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)佛吓,可以分為屬性和方法兩個部分,其中屬性是我們需要重點關(guān)注的
defaultValueForKey
和shouldArchiveValueForKey
這兩個方法從名字上看就知道跟NSCoding
協(xié)議脫不開干系垂攘,后者通過傳入一個關(guān)鍵字對動畫對象進行序列化本地存儲维雇,并且返回成功與否。然后使用相同的關(guān)鍵字調(diào)用前者來獲取這個持久化的動畫對象-
timingFunction
這個是個有趣的屬性晒他,決定了動畫的視覺效果吱型。我在從UIView動畫說起中提到過動畫的視覺效果,包括淡入
陨仅、淡出
等效果津滞,這些效果用字符串表示: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)
動畫時間曲線 delegate
在NSObject
中實現(xiàn)了CAAnimation
的回調(diào)方法,包括不限于animationDidStart
和animationDidStop
等方法灼伤。因此在iOS中的任何一個對象都能成為CAAnimation
的代理人触徐。通過這個代理我們可以在動畫結(jié)束時移除動畫效果等操作removedOnCompletion
決定了動畫結(jié)束之后是否將動畫從相應(yīng)的圖層上移除,默認(rèn)是true
狐赡。由于圖層動畫實際上相當(dāng)于障眼法(使用CAAnimaiton
實現(xiàn)動畫效果的時候并不會真的修改對應(yīng)的屬性)锌介,即是在動畫結(jié)束時圖層會回到動畫開始的狀態(tài)。通過設(shè)置這個值為false
以及其他配置猾警,可以避免這種事情發(fā)生
動畫協(xié)議屬性
除了CAAnimation
本身的屬性之外,另外兩個協(xié)議中聲明了決定動畫時長隆敢、前后動畫效果等關(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
是一個控制動畫時間的協(xié)議发皿,提供了動畫過程中的時間相關(guān)的屬性,對于這些屬性在控制動畫時間一文中講解的非常清楚了拂蝎,筆者在這里就不再一一介紹穴墅。此外,還有另一個協(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]?)
}
在動畫發(fā)生之后這個方法會被調(diào)用温自,這個方法把將要發(fā)生的事件告訴圖層玄货,從而讓圖層做出對應(yīng)的操作,比如渲染等悼泌。
CAAction
顯式動畫
開頭筆者提到過隱式動畫是單獨的layer
的可動畫屬性發(fā)生改變時自動產(chǎn)生的過度動畫松捉,那么肯定就有對應(yīng)的顯式動畫。顯式動畫的制作過程是創(chuàng)建一個動畫對象馆里,然后添加到實現(xiàn)動畫的圖層上隘世。下面這段代碼創(chuàng)建了一個修改layer.position.y
的基礎(chǔ)動畫對象可柿,然后設(shè)置動畫結(jié)束值為160
后添加到layer
層上,動畫的默認(rèn)時長是0.25
秒:
let animation = CABasicAnimation(keyPath: "position.y")
animation.toValue = NSNumber(float: 160)
layer.position.y = 160
layer.addAnimation(animation, forKey: nil)
對于動畫的更詳細(xì)講解不在本篇文章的計劃內(nèi)丙者,在接下來的核心動畫中筆者會更加詳細(xì)的介紹各式各樣的CAAnimation
子類以用于不同的動畫場景复斥。這里放上上面粘性彈窗的核心代碼:
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")
}
給三個圖層添加了下移的動畫之后,創(chuàng)建CADisplayLink
定時器來同步屏幕刷新頻率更新彈出效果:
@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
}
隱式動畫發(fā)生了什么
上面說過隱式動畫發(fā)生在單獨的CALayer
對象的可動畫屬性被改變時械媒。如果我們這個layer
已經(jīng)存在與之綁定的UIView
對象目锭,那么當(dāng)我們直接修改這個layer
的屬性的時候,只會瞬間從舊值
變成新值
的顯示效果纷捞,不會有額外的效果痢虹。在CoreAnimation
的編程指南中對此做出了解釋:UIView默認(rèn)情況下禁止了layer動畫,但在animate block中重新啟用了它們
兰绣。這是我們看到的行為世分,但如果認(rèn)真去挖掘這一機制的內(nèi)部實現(xiàn),我們會驚訝于view
和layer
之間協(xié)同工作的精心設(shè)計缀辩,這里就要提到CAAction
當(dāng)任何一個可動畫的
layer
的屬性發(fā)生改變的時候臭埋,layer
通過向它的代理人發(fā)送actionForLayer(layer:event:)
方法來查詢一個對應(yīng)屬性變化的CAAction
對象,這個方法可以返回下面三個結(jié)果:
- 返回一個遵循
CAAction
的對象臀玄,這種情況下layer
使用這個動作完成動畫 - 返回一個
nil
瓢阴,這樣layer
就會到其他地方繼續(xù)尋找 - 返回一個
NSNull
對象,這樣layer
就會停止查找并且不執(zhí)行動畫
正常來說健无,當(dāng)一個CALayer
和UIView
關(guān)聯(lián)的時候荣恐,這個UIView
對象會成為layer
的代理人。因此從返回值上來說累贤,當(dāng)layer
的屬性被我們修改的時候叠穆,這個關(guān)聯(lián)的UIView
對象一般都是直接返回NSNull
對象,而只有在animate block
的狀態(tài)下才會返回實際的動畫效果臼膏,方便讓圖層繼續(xù)查找處理動作的方案硼被。我們通過代碼來驗證:
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"))")
}
控制臺輸出結(jié)果如下,在動畫block
中確實返回了一個CABasicAnimation
對象來協(xié)同完成這個動畫效果
===========normal call===========
Optional(<null>)
===========animate block call===========
Optional(<CABasicAnimation:0x7f8712d30c90; delegate = <UIViewAnimationState: 0x7f8712d304d0>; fillMode = both; timingFunction = easeInEaseOut; duration = 0.25; fromValue = 1; keyPath = opacity>)
通常來說處在動畫代碼塊中的UIView
都會返回這么一個核心動畫對象渗磅,但如果返回的是nil
嚷硫,圖層還有繼續(xù)查找其他的動作解決方案,整個的查找過程共有四次始鱼,這個在CALayer
的頭文件中已經(jīng)說明了:
當(dāng)
layer
對象查找到了屬性修改動作的動畫時仔掸,就會調(diào)用addAnimation(_:forKey:)
方法開始執(zhí)行動畫效果。同樣的医清,我們繼承CALayer
對象來重寫這個方法:
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
}
控制臺輸出結(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>
這里可能會有人有疑惑起暮,為什么兩次輸出的CABasicAnimation
的地址不一樣。為了保證同一個動畫對象可以作用于多個CALayer
對象執(zhí)行状勤,在addAnimation(_:forKey:)
方法調(diào)用的時候都會對CAAnimation
對象進行一次copy
操作鞋怀。各位可以繼承CABasicAnimation
對象重寫copy
方法自行測試
尾言
本來筆者想要直接使用動畫粒子開始講解核心動畫這一框架双泪,但是考慮到如果能夠全面的對核心動畫進行一次講解,這對于以后的文章講解以及動畫粒子的制作有很大的幫助密似。本文demo
轉(zhuǎn)載請注明本文作者和轉(zhuǎn)載地址