自己做的筆記,過(guò)段時(shí)間卻又忘記了,為了盡量避免這種情況快毛,同時(shí)治服拖延種種格嗅,打算開(kāi)始多寫(xiě)寫(xiě)技術(shù)向的博客。
UIKit Animation or Core Animation?
在 iOS 中唠帝,動(dòng)畫(huà)可以分為兩個(gè)類(lèi)別:UIKit屯掖,和底層的 Core Animation。
通過(guò)最基本的 UIView.animate(withDuration:)
方法建立如下的 UIKit animation襟衰,能勝任復(fù)雜程度不高的動(dòng)畫(huà):
UIView.animate(withDuration: 0.8) {
self.orangeBlock.frame = CGRect(x: 38, y: 70, width: 300, height: 60)
}
但是當(dāng)要處理不單一的屬性時(shí)贴铜,往往會(huì)出現(xiàn)一些不合預(yù)期的效果,這是因?yàn)槠偕梗?UIKit 接口創(chuàng)建的動(dòng)畫(huà)是基于視圖(view)的绍坝,它并不能直接對(duì)圖層(layer)屬性作出更改。
為了制作出能符合預(yù)期的動(dòng)畫(huà)效果瑰妄,我們就需要直接對(duì)圖層進(jìn)行控制陷嘴,這就是 Core Animation 了。
基于 Core Animation间坐,我們可以創(chuàng)建兩步的 CABasicAnimation
灾挨,或者用 CAKeyframAnimation
繪制路徑等。
Layer v.s. View
圖層不是視圖的替代竹宋,而是后者的底層支持劳澄。在能滿足需求的情況下,對(duì)視圖而不是圖層進(jìn)行操作——即使用 UIKit 動(dòng)畫(huà)——是被建議的做法蜈七。
在 iOS 開(kāi)發(fā)中秒拔,通常是每一個(gè)視圖有一個(gè)對(duì)應(yīng)的圖層(稱(chēng)為 layer-backed view),圖層對(duì)象會(huì)保證兩者保持同步飒硅。然而在一些特殊情況下砂缩,這種一對(duì)一的關(guān)系不是必須的(比如多個(gè)圖層支持一個(gè)單獨(dú) UIImageView 來(lái)節(jié)省因單張圖片重復(fù)出現(xiàn)的內(nèi)存占用)。
基本過(guò)渡動(dòng)畫(huà) - CABasicAnimation
想要對(duì)哪一組值進(jìn)行設(shè)置三娩,知道對(duì)應(yīng)的 keyPath 就好了庵芭。
let radiusAnimation = CABasicAnimation(keyPath: "cornerRadius")
radiusAnimation.fromValue = 50
radiusAnimation.toValue = 30
radiusAnimation.duration = 3
purpleBlock.layer.add(radiusAnimation, forKey: "cornerRadius")
// 為模型圖層設(shè)置新的半徑
purpleBlock.layer.cornerRadius = 30
需要注意的一點(diǎn)是,add(_: CAAnimation, forKey:)
設(shè)置的動(dòng)畫(huà)針對(duì)的是模型圖層(model layer)而不是顯示圖層(presentation layer)雀监,當(dāng)動(dòng)畫(huà)顯示完双吆,它就被移除了,顯示圖層屬性將會(huì)恢復(fù)成初始的模型圖層屬性会前。所以好乐,為了保持對(duì)象視圖變化后的狀態(tài),要記得手動(dòng)更新瓦宜。
Core Animation 控制的圖層中包含有兩套平行的繼承樹(shù):模型圖層樹(shù)(model layer tree)和顯示圖層樹(shù)(presentation layer tree)蔚万。前者反映的是圖層狀態(tài),后者是圖層在動(dòng)畫(huà)中動(dòng)態(tài)的值临庇。如果要實(shí)時(shí)跟蹤圖層的屬性變化笛坦,要檢測(cè)的是顯示圖層的值区转。
關(guān)鍵幀動(dòng)畫(huà) - CAKeyframeAnimation
現(xiàn)在,試想我們要實(shí)現(xiàn)的動(dòng)畫(huà)超過(guò)了兩步(而這是非常常見(jiàn)的情況)版扩,那么以 fromValue
和 toValue
簡(jiǎn)單兩組值就顯然過(guò)于局限了废离。這時(shí)候我們用 CAKeyframeAnimation
就能任意地定義和設(shè)置幀。
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ù)組代表的是對(duì)象的位置礁芦,keyTimes
數(shù)組代表劃分的時(shí)間段蜻韭。
除了使用狀態(tài)數(shù)組的方式,設(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 的可重用性
一個(gè)動(dòng)畫(huà)實(shí)例由 keyPath 指定其所定義的圖層屬性類(lèi)型柿扣,因此對(duì)于該屬性(如 cornerRadius)不必再重復(fù)創(chuàng)建新實(shí)例肖方,也即是能被多個(gè)對(duì)象所共用的。
為了提高其可重用性未状,我們還可以用 byValue
來(lái)替代 CABasicAnimation
中的 toValue
俯画,前者設(shè)定的是變化量。
let radiusAnimation = CABasicAnimation(keyPath: "cornerRadius")
radiusAnimation.fromValue = 50
// radiusAnimation.toValue = 30
// equals:
radiusAnimation.byValue = -20
如果同時(shí)創(chuàng)建了多個(gè)動(dòng)畫(huà)司草,可以使用 CAAnimationGroup
打包成一個(gè)動(dòng)畫(huà)組:
let aniGroup = CAAnimationGroup()
aniGroup.animations = [shakeAnimation, radiusAnimation]
aniGroup.duration = 3
?
magentaBlock.layer.add(aniGroup, forKey: "whatsoever")
Timing Functions 使動(dòng)畫(huà)流暢
為了讓動(dòng)畫(huà)看起來(lái)更自然艰垂,添加一個(gè) CAMediaTimingFunction
對(duì)象實(shí)例÷窈纾可以直接對(duì)動(dòng)畫(huà)實(shí)例設(shè)置:
// 現(xiàn)有的「淡入淡出」效果
let timingFunc1 = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
ANI_INSTANCE.timingFunction = timingFunc
也可以為 CATransaction
設(shè)置:
// 自定義函數(shù)猜憎,通過(guò) controlPoints 設(shè)置貝塞爾曲線
let timingFunc2 = CAMediaTimingFunction(controlPoints: 0.65, -0.55, 0.27, 1.55)
CATransaction.setAnimationTimingFunction(timingFunc)
CATransaction 事務(wù)機(jī)制
當(dāng)我們要進(jìn)行多組圖層操作時(shí),一個(gè)很好的習(xí)慣是把它們顯式(explicitly)合并到一個(gè)事務(wù)中搔课。
CATransaction Definition: A mechanism for grouping multiple layer-tree operations into atomic updates to the render tree.
事實(shí)上胰柑,即使我們不主動(dòng)創(chuàng)建 CATransaction 事務(wù),系統(tǒng)也會(huì)默認(rèn)給我們創(chuàng)建一個(gè)隱式(implicit)事務(wù)爬泥。顯式聲明還有個(gè)好處是柬讨,可以為事務(wù)內(nèi)部的代碼統(tǒng)一設(shè)置一些默認(rèn)值(如 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
- 設(shè)計(jì)模式 - 菜鳥(niǎo)教程
- 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