iOS動畫-認(rèn)識CoreAnimation

前言

在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)動畫的位圖上下文荸实;后者是一個抽象的動畫基類匀们,它提供了對CAMediaTimingCAAction協(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)容,它自身管理著我們提供給視圖顯示的位圖上下文以及保存這些位圖上下文的幾何信息沦辙。通過上面的代碼可以看出:

  • CALayerNSObject的子類而非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)操作

對于蘋果為什么要把UIViewCALayer區(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é)合 modelLayerpresentationLayer中設(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ì)的講解:

  • positionanchorPoint
    anchorPoint是一個xy值取值范圍內(nèi)在0~1之間CGPoint類型,它決定了當(dāng)圖層發(fā)生幾何仿射變換時基于的坐標(biāo)原點阿蝶。默認(rèn)情況下為0.5, 0.5雳锋,由anchorPointframe經(jīng)過計算獲得圖層的position這個值。更多介紹這兩個屬性的文章在徹底理解position和anchorPoint

  • maskmaskToBounds
    maskToBounds值為true時表示超出圖層范圍外的所有子圖層都不會進行渲染赡磅,當(dāng)我們設(shè)置UIViewclipsToBounds時實際上就是在修改maskToBounds這個屬性魄缚。mask這個屬性表示一個遮罩圖層,在這個遮罩之外的內(nèi)容不予渲染顯示焚廊,在上一篇動畫碎片動畫中使用的maskView實際上也是修改這個屬性

  • cornerRadius冶匹、borderWidthborderColor
    borderWidthborderColor設(shè)置了圖層的邊緣線條的顏色以及寬度,正常情況下這兩個屬性在layer的層次上不怎么使用咆瘟。后者cornerRadius設(shè)置圓角半徑嚼隘,這個半徑會影響邊緣線條的形狀

  • shadowColorshadowOpacity袒餐、shadowOffsetshadowRadius
    這四個屬性結(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)更多的幾何變換效果盈罐。此外還有boundframe這些影響圖層顯示范圍的屬性,就不再多說

CAAnimation

CAAnimation的子類

開頭說過闪唆,CAAnimation是一個封裝出來的基類盅粪,其最重要的目的在于遵循兩個重要的動畫相關(guān)協(xié)議,所以解析動畫類型要從它的子類依賴關(guān)系下手悄蕾。在蘋果文檔中票顾,CAAnimation的直接子類包括這些:

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)生成了settergetter

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)注的

  • defaultValueForKeyshouldArchiveValueForKey
    這兩個方法從名字上看就知道跟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)方法,包括不限于animationDidStartanimationDidStop等方法灼伤。因此在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),我們會驚訝于viewlayer之間協(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)一個CALayerUIView關(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)載地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末焙矛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子残腌,更是在濱河造成了極大的恐慌村斟,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抛猫,死亡現(xiàn)場離奇詭異蟆盹,居然都是意外死亡,警方通過查閱死者的電腦和手機闺金,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門逾滥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人败匹,你說我怎么就攤上這事寨昙。” “怎么了掀亩?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵舔哪,是天一觀的道長。 經(jīng)常有香客問我槽棍,道長捉蚤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任炼七,我火速辦了婚禮缆巧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘豌拙。我一直安慰自己盅蝗,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布姆蘸。 她就那樣靜靜地躺著,像睡著了一般芙委。 火紅的嫁衣襯著肌膚如雪逞敷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天灌侣,我揣著相機與錄音推捐,去河邊找鬼。 笑死侧啼,一個胖子當(dāng)著我的面吹牛牛柒,可吹牛的內(nèi)容都是我干的堪簿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼皮壁,長吁一口氣:“原來是場噩夢啊……” “哼椭更!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蛾魄,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤虑瀑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后滴须,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舌狗,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年扔水,在試婚紗的時候發(fā)現(xiàn)自己被綠了痛侍。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡魔市,死狀恐怖主届,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嘹狞,我是刑警寧澤岂膳,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站磅网,受9級特大地震影響谈截,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涧偷,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一簸喂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧燎潮,春花似錦喻鳄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至爪喘,卻和暖如春颜曾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背秉剑。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工泛豪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓诡曙,卻偏偏與公主長得像臀叙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子价卤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動畫效果劝萤,實現(xiàn)這些動畫的過程并不復(fù)雜,今天將帶大家一窺ios動畫全貌荠雕。在這里你可以看...
    每天刷兩次牙閱讀 8,495評論 6 30
  • 在iOS中隨處都可以看到絢麗的動畫效果稳其,實現(xiàn)這些動畫的過程并不復(fù)雜,今天將帶大家一窺iOS動畫全貌炸卑。在這里你可以看...
    F麥子閱讀 5,113評論 5 13
  • 書寫的很好既鞠,翻譯的也棒!感謝譯者盖文,感謝感謝嘱蛋! iOS-Core-Animation-Advanced-Techni...
    錢噓噓閱讀 2,298評論 0 6
  • Core Animation Core Animation,中文翻譯為核心動畫五续,它是一組非常強大的動畫處理API洒敏,...
    45b645c5912e閱讀 3,032評論 0 21
  • 在iOS實際開發(fā)中常用的動畫無非是以下四種:UIView動畫,核心動畫疙驾,幀動畫凶伙,自定義轉(zhuǎn)場動畫。 1.UIView...
    請叫我周小帥閱讀 3,098評論 1 23