iOS 核心動畫

自己做的筆記,過段時間卻又忘記了,為了盡量避免這種情況,同時治服拖延種種,打算開始多寫寫技術向的博客。


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)
}![image.png](https://upload-images.jianshu.io/upload_images/8048781-4b3a1db18a8c7915.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

但是當要處理不單一的屬性時,往往會出現(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)存占用)。

image

基本過渡動畫 - 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)的動畫超過了兩步(而這是非常常見的情況),那么以 fromValuetoValue 簡單兩組值就顯然過于局限了。這時候我們用 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:

  1. Design Patterns on iOS using Swift - Part 1/2 - raywenderlich
  2. Design Patterns on iOS using Swift – Part 2/2 - raywenderlich
  3. 設計模式 - 菜鳥教程
  4. KVO - Swifter
  5. Key-Value Observing Programming Guide - Apple
  6. Is key-value observation (KVO) available in Swift? - Stack Overflow
  7. Exploring KVO alternatives with Swift - Scott Logic
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容