自己做的筆記,過段時間卻又忘記了,為了盡量避免這種情況,同時治服拖延種種,打算開始多寫寫技術向的博客。
UIKit Animation or Core Animation?
在 iOS 中,動畫可以分為兩個類別:UIKit,和底層的 Core Animation。
通過最基本的 UIView.animate(withDuration:) 方法建立如下的 UIKit animation,能勝任復雜程度不高的動畫:
UIView.animate(withDuration: 0.8) {
self.orangeBlock.frame = CGRect(x: 38, y: 70, width: 300, height: 60)
}
但是當要處理不單一的屬性時,往往會出現(xiàn)一些不合預期的效果,這是因為,用 UIKit 接口創(chuàng)建的動畫是基于視圖(view)的,它并不能直接對圖層(layer)屬性作出更改。
為了制作出能符合預期的動畫效果,我們就需要直接對圖層進行控制,這就是 Core Animation 了。
基于 Core Animation,我們可以創(chuàng)建兩步的 CABasicAnimation,或者用 CAKeyframAnimation 繪制路徑等。

Layer v.s. View
圖層不是視圖的替代,而是后者的底層支持。在能滿足需求的情況下,對視圖而不是圖層進行操作——即使用 UIKit 動畫——是被建議的做法。
在 iOS 開發(fā)中,通常是每一個視圖有一個對應的圖層(稱為 layer-backed view),圖層對象會保證兩者保持同步。然而在一些特殊情況下,這種一對一的關系不是必須的(比如多個圖層支持一個單獨 UIImageView 來節(jié)省因單張圖片重復出現(xiàn)的內(nèi)存占用)。

基本過渡動畫 - CABasicAnimation
想要對哪一組值進行設置,知道對應的 keyPath 就好了。
let radiusAnimation = CABasicAnimation(keyPath: "cornerRadius")
radiusAnimation.fromValue = 50
radiusAnimation.toValue = 30
radiusAnimation.duration = 3
purpleBlock.layer.add(radiusAnimation, forKey: "cornerRadius")
// 為模型圖層設置新的半徑
purpleBlock.layer.cornerRadius = 30
需要注意的一點是,add(_: CAAnimation, forKey:) 設置的動畫針對的是模型圖層(model layer)而不是顯示圖層(presentation layer),當動畫顯示完,它就被移除了,顯示圖層屬性將會恢復成初始的模型圖層屬性。所以,為了保持對象視圖變化后的狀態(tài),要記得手動更新。
Core Animation 控制的圖層中包含有兩套平行的繼承樹:模型圖層樹(model layer tree)和顯示圖層樹(presentation layer tree)。前者反映的是圖層狀態(tài),后者是圖層在動畫中動態(tài)的值。如果要實時跟蹤圖層的屬性變化,要檢測的是顯示圖層的值。
關鍵幀動畫 - CAKeyframeAnimation
現(xiàn)在,試想我們要實現(xiàn)的動畫超過了兩步(而這是非常常見的情況),那么以 fromValue 和 toValue 簡單兩組值就顯然過于局限了。這時候我們用 CAKeyframeAnimation 就能任意地定義和設置幀。
let shakeAnimation = CAKeyframeAnimation(keyPath: "position.x")
shakeAnimation.values = [0, 10, -10, 10, 0]
shakeAnimation.keyTimes = [0, 0.2, 0.6, 0.8, 1]
shakeAnimation.duration = 0.4
shakeAnimation.isAdditive = true
blueBlock.layer.add(shakeAnimation, forKey: "position.x")
values 數(shù)組代表的是對象的位置,keyTimes 數(shù)組代表劃分的時間段。
除了使用狀態(tài)數(shù)組的方式,設定路徑(CGPath)也是可以的:
let boundingRect = CGRect(x: -150, y: -150, width: 300, height: 300)
let orbitAnimation = CAKeyframeAnimation(keyPath: "position")
orbitAnimation.path = CGPath(ellipseIn: boundingRect, transform: nil)
orbitAnimation.duration = 4
orbitAnimation.isAdditive = true
orbitAnimation.repeatCount = Float.infinity
orbitAnimation.calculationMode = kCAAnimationPaced
orbitAnimation.rotationMode = kCAAnimationRotateAuto
?
greyBlock.layer.add(orbitAnimation, forKey: "position")
CAAnimation 的可重用性
一個動畫實例由 keyPath 指定其所定義的圖層屬性類型,因此對于該屬性(如 cornerRadius)不必再重復創(chuàng)建新實例,也即是能被多個對象所共用的。
為了提高其可重用性,我們還可以用 byValue 來替代 CABasicAnimation 中的 toValue,前者設定的是變化量。
let radiusAnimation = CABasicAnimation(keyPath: "cornerRadius")
radiusAnimation.fromValue = 50
// radiusAnimation.toValue = 30
// equals:
radiusAnimation.byValue = -20
如果同時創(chuàng)建了多個動畫,可以使用 CAAnimationGroup 打包成一個動畫組:
let aniGroup = CAAnimationGroup()
aniGroup.animations = [shakeAnimation, radiusAnimation]
aniGroup.duration = 3
?
magentaBlock.layer.add(aniGroup, forKey: "whatsoever")
Timing Functions 使動畫流暢
為了讓動畫看起來更自然,添加一個 CAMediaTimingFunction 對象實例??梢灾苯訉赢媽嵗O置:
// 現(xiàn)有的「淡入淡出」效果
let timingFunc1 = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
ANI_INSTANCE.timingFunction = timingFunc
也可以為 CATransaction 設置:
// 自定義函數(shù),通過 controlPoints 設置貝塞爾曲線
let timingFunc2 = CAMediaTimingFunction(controlPoints: 0.65, -0.55, 0.27, 1.55)
CATransaction.setAnimationTimingFunction(timingFunc)
CATransaction 事務機制
當我們要進行多組圖層操作時,一個很好的習慣是把它們顯式(explicitly)合并到一個事務中。
CATransaction Definition: A mechanism for grouping multiple layer-tree operations into atomic updates to the render tree.
事實上,即使我們不主動創(chuàng)建 CATransaction 事務,系統(tǒng)也會默認給我們創(chuàng)建一個隱式(implicit)事務。顯式聲明還有個好處是,可以為事務內(nèi)部的代碼統(tǒng)一設置一些默認值(如 duration)。嵌套的 CATransaction 是允許的。
CATransaction.begin()
CATransaction.setAnimationDuration(3)
let positionAnimation = CABasicAnimation(keyPath: "position")
positionAnimation.fromValue = [38, 484]
positionAnimation.toValue = [112, 484]
greenBlockLayer.add(positionAnimation, forKey: "position")
greenBlockLayer.position = CGPoint(x: 112, y: 484)
CATransaction.begin()
CATransaction.setAnimationDuration(1)
?
UIView.animate(withDuration: b_Interval) {
self.greenBlock.alpha = 0.5
}
?
CATransaction.commit()
CATransaction.commit()
ref:
- Design Patterns on iOS using Swift - Part 1/2 - raywenderlich
- Design Patterns on iOS using Swift – Part 2/2 - raywenderlich
- 設計模式 - 菜鳥教程
- KVO - Swifter
- Key-Value Observing Programming Guide - Apple
- Is key-value observation (KVO) available in Swift? - Stack Overflow
- Exploring KVO alternatives with Swift - Scott Logic