本文是我學習《iOS Animations by Tutorials》 筆記中的一篇。 文中詳細代碼都放在個人Github上 andyRon/LearniOSAnimations。html
這個部分介紹UIKit動畫API,這些API專門用於輕鬆製做視圖動畫(View Animations),同時避免核心動畫(Core Animation)(見系統學習iOS動畫之三:圖層動畫)的複雜性。ios
UIKit動畫API不只易於使用,並且提供了大量靈活性和強大功能,能夠處理大多數(固然不是所有)動畫要求。git
UIKit動畫API能夠在屏幕上爲最終繼承自UIView
的任何對象設置動畫,例如:UILabel
,UIImageView
,UIButton
等等,也能夠是本身建立的任何自定義最終繼承自UIView
類。github
本文包括五個章節,完成兩個項目BahamaAirLoginScreen和Flight Info。swift
BahamaAirLoginScreen 是一個登陸頁面項目,一、二、3章節爲這個項目的一些UI添加各類動畫。數組
1-視圖動畫入門 —— 學習如何移動,縮放和淡化視圖等基本的UIKit API。
2-彈簧動畫 —— 在線性動畫的概念基礎上,使用彈簧動畫創造出更引人注目的效果。😊
3-過渡動畫 —— 視圖的出現和消失。bash
Flight Info 是一個航班狀態變化項目,四、5章節用一些高級一點動畫來完成這個項目。網絡
4-練習視圖動畫 —— 練習前面學到的動畫技術。
5-關鍵幀動畫 —— 使用關鍵幀動畫來建立由許多不一樣階段組成的複雜動畫。閉包
開始項目 BahamaAirLoginScreen是一個簡單的登陸頁面,有兩個TextField,一個Label,一個Button,4個雲圖片和一個背景圖片,效果以下:app
讓Label和兩個TextField在視圖顯示以前移動到屏幕外。在viewWillAppear()
中添加:
heading.center.x -= view.bounds.width
username.center.x -= view.bounds.width
password.center.x -= view.bounds.width
複製代碼
添加Label和兩個TextField進入屏幕的動畫,在viewDidAppear()
中添加:
UIView.animate(withDuration: 0.5) {
self.heading.center.x += self.view.bounds.width
}
UIView.animate(withDuration: 0.5, delay: 0.3, options: [],
animations: {
self.username.center.x += self.view.bounds.width
},
completion: nil
)
UIView.animate(withDuration: 0.5, delay: 0.4, options: [],
animations: {
self.password.center.x += self.view.bounds.width
},
completion: nil
)
複製代碼
這樣heading和TextField就有了先後分別進入屏幕的動畫。
相似UIView.animate(...)
的方法,根據參數的不一樣有好幾個,不一樣參數的意義:
withDuration
:動畫持續時間。
delay
:動畫開始以前的延遲時間。
options
:UIView.AnimationOptions
的數組,用來定義動畫的變化形式,以後會詳細說明。
animations
:提供動畫的閉包,也就是動畫代碼。
completion
:動畫執行完成後的閉包 。
還有 usingSpringWithDamping
和initialSpringVelocity
以後章節會提到。
前面,使用center
建立簡單的位置變化視圖動畫。
並不是全部視圖屬性均可以設置動畫,但全部視圖動畫(從最簡單到最複雜)均可以經過動畫視圖上的屬性來構建。下面來看看可用於動畫的屬性有哪些:
bounds
frame
center
backgroundColor
alpha
: 可建立淡入和淡出效果。
transform
: 設置視圖的旋轉,縮放和/或位置的動畫。
這些看起來像是很是基本的動畫,能夠製做使人驚訝的複雜動畫效果!😉
動畫選項(Animation options)就是以前提到的options
參數,它是UIView.AnimationOptions
的數組。UIView.AnimationOptions
是結構體,有不少常量值,具體可查看官方文檔 。
下面說明幾個經常使用的
.repeat
:動畫一直重複。
.autoreverse
:若是僅有.repeat
參數動畫的過程,就像是 b->e b->e ...
,而有了.autoreverse
,動畫過程就像是b->e->b->e ...
。看下圖很容易看出區別。
Animation easing,我暫且把它叫作 動畫緩動。
curve:彎曲;使彎曲。ease:減輕,緩和。
在現實生活中,事物並不僅是忽然開始或中止移動。 像汽車或火車這樣的物體會慢慢加速直到達到目標速度,除非它們碰到磚牆,不然它們會逐漸減速直到它們徹底停在最終目的地。
爲了使動畫看起來更逼真,能夠在開始時慢慢加速,在結束前放慢速度,通常稱爲緩入(ease-in)和緩出(ease-out)。
.curveLinear
:不對動畫應用加速或減速。 .curveEaseIn
:動畫的開始時慢,結束時快。
UIView.animate(withDuration: 1, delay: 0.6, options: [.repeat, .autoreverse, .curveEaseIn], animations: {
self.password.center.x += self.view.bounds.width
}, completion: nil)
複製代碼
.curveEaseOut
:動畫開始時快,結束時慢。
UIView.animate(withDuration: 1, delay: 0.6, options: [.repeat, .autoreverse, .curveEaseOut], animations: {
self.password.center.x += self.view.bounds.width
}, completion: nil)
複製代碼
.curveEaseInOut
:動畫開始結束都慢,中間快
這個很好理解,就是雲的UIImageView
的透明度變化動畫。先在viewWillAppear()
中把雲設置成透明:
cloud1.alpha = 0.0
cloud2.alpha = 0.0
cloud3.alpha = 0.0
cloud4.alpha = 0.0
複製代碼
而後在viewDidAppear()
中添加動畫。
UIView.animate(withDuration: 0.5, delay: 0.5, options: [], animations: {
self.cloud1.alpha = 1.0
}, completion: nil)
UIView.animate(withDuration: 0.5, delay: 0.7, options: [], animations: {
self.cloud2.alpha = 1.0
}, completion: nil)
UIView.animate(withDuration: 0.5, delay: 0.9, options: [], animations: {
self.cloud3.alpha = 1.0
}, completion: nil)
UIView.animate(withDuration: 0.5, delay: 1.1, options: [], animations: {
self.cloud4.alpha = 1.0
}, completion: nil)
複製代碼
1-視圖動畫入門中動畫是單一方向上的動做,能夠理解爲一點移動到另外一個。
這一章節是稍微複雜一點的彈簧動畫(Springs):
用點變化描述彈簧動畫:
視圖從A點到B點,在B點來回遞減振盪,直到視圖在B點中止。這是一個很好的效果, 讓咱們的動畫添加了一種活潑,真實的感受。
本章的開始項目 BahamaAirLoginScreen是上一章節的完成項目。
在viewWillAppear()
中添加:
loginButton.center.y += 30.0
loginButton.alpha = 0.0
複製代碼
而後再在viewDidAppear()
中添加:
UIView.animate(withDuration: 0.5, delay: 0.5,
usingSpringWithDamping: 0.5, initialSpringVelocity: 0.0, options: [], animations: {
self.loginButton.center.y -= 30.0
self.loginButton.alpha = 1.0
}, completion: nil)
複製代碼
這樣Log In按鈕就有個向上移動的動畫變成了兩個屬性同時變化的動畫。
usingSpringWithDamping
:阻尼參數, 介於0.0 ~ 1.0,接近0.0的值建立一個更有彈性的動畫,而接近1.0的值建立一個看起來很僵硬的效果。 您能夠將此值視爲彈簧的**「剛度」**。
initialSpringVelocity
: 初始速度, 要平滑開始動畫,請將此值與視圖以前的視圖速度相匹配。
效果:
讓登陸按鈕產生一個與用戶交互的動畫,在Log In按鈕的Action logIn()
方法中添加:
UIView.animate(withDuration: 1.5, delay: 0.0, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: {
self.loginButton.bounds.size.width += 80.0
}, completion: nil)
複製代碼
點擊後有個寬度變大的簡單動畫。
繼續在logIn()
中添加:
UIView.animate(withDuration: 0.33, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.0, options: [], animations: {
self.loginButton.center.y += 60.0
}, completion: nil)
複製代碼
點擊後寬度變大的同時向下移動移動位置。
給用戶反饋的另外一個好方法是經過顏色變化。 在上面動畫閉包中添加:
self.loginButton.backgroundColor = UIColor(red: 0.85, green: 0.83, blue: 0.45, alpha: 1.0)
複製代碼
最後一個給用戶反饋的方法:activity indicator(活動指示器,俗稱菊花轉😅)。 登陸按鈕應該經過網絡啓動用戶身份驗證活動,經過菊花轉讓用戶知道登陸操做正在進行。
繼續在上面動畫閉包中添加(spinner
已經在viewDidLoad
中初始化了,而且alpha
設置爲0.0):
self.spinner.center = CGPoint(x: 40.0, y: self.loginButton.frame.size.height/2)
self.spinner.alpha = 1.0
複製代碼
讓菊花轉也隨着登陸按鈕的向下移動而移動,最終登陸按鈕的效果:
把以前viewDidAppear()
中的
UIView.animate(withDuration: 0.5, delay: 0.3, options: [], animations: {
self.username.center.x += self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 1, delay: 0.6, options: [], animations: {
self.password.center.x += self.view.bounds.width
}, completion: nil)
複製代碼
修改成:
UIView.animate(withDuration: 0.5, delay: 0.3, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.0, options: [], animations: {
self.username.center.x += self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 0.5, delay: 0.4, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.0, options: [], animations: {
self.password.center.x += self.view.bounds.width
}, completion: nil)
複製代碼
效果爲:
過渡動畫(Transitions)
本章節的開始項目 是前一章節的完成項目。
使用過渡動畫的各類動畫場景。
要在屏幕上添加新視圖的動畫,能夠調用相似於前面章節中使用的方法。 此次的不一樣之處在於,須要預先選擇一個預約義的過渡效果,併爲動畫容器視圖設置動畫。 過渡動畫是設置在容器視圖上,所以動畫做用在添加到容器視圖的全部子視圖。
下面作一個測試(結束後,刪除相應代碼繼續以後內容):
var animationContainerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
//設置動畫容器視圖
animationContainerView = UIView(frame: view.bounds)
animationContainerView.frame = view.bounds
view.addSubview(animationContainerView)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//建立新視圖
let newView = UIImageView(image: UIImage(named: "banner"))
newView.center = animationContainerView.center
//經過過渡動畫增長新視圖
UIView.transition(with: animationContainerView,
duration: 0.33,
options: [.curveEaseOut, .transitionFlipFromBottom],
animations: {
self.animationContainerView.addSubview(newView)
},
completion: nil
)
}
複製代碼
效果:
transitionFlipFromBottom
被transitionFlipFromLeft
替代後的效果:
完整的預約義過渡動畫的選項以下,這些動畫選項和上兩節中出現options
同樣屬於UIView.AnimationOptions
。
.transitionFlipFromLeft
.transitionFlipFromRight
.transitionCurlUp
.transitionCurlDown
.transitionCrossDissolve
.transitionFlipFromTop
.transitionFlipFromBottom
複製代碼
從屏幕中刪除子視圖的過渡動畫操做和添加相似。
參考代碼:
UIView.transition(with: animationContainerView, duration: 0.33,
options: [.curveEaseOut, .transitionFlipFromBottom],
animations: {
self.newView.removeFromSuperview()
},
completion: nil
)
複製代碼
添加和刪除都會改變視圖層次結構,這也是須要一個容器視圖的緣由。隱藏或顯示的過渡動畫使用視圖自己做爲動畫容器。
參考代碼:
UIView.transition(with: self.newView, duration: 0.33,
options: [.curveEaseOut, .transitionFlipFromBottom],
animations: {
self.newView.isHidden = true
},
completion: nil
)
複製代碼
參考代碼:
UIView.transition(from: oldView, to: newView, duration: 0.33,
options: .transitionFlipFromTop, completion: nil)
複製代碼
這一部分將模擬一些用戶身份驗證過程,幾個不一樣的進度消息變化的動畫。 一旦用戶點擊登陸按鈕,將向他們顯示消息,包括「Connecting...」,「Authorizing」和「Failed」。
在ViewController
中添加方法showMessage()
:
func showMessage(index: Int) {
label.text = messages[index]
UIView.transition(with: status, duration: 0.33, options: [.curveEaseOut, .transitionCurlDown], animations: {
self.status.isHidden = false
}, completion: { _ in
})
}
複製代碼
並在登陸按鈕的ActionlogIn
方法的下移動畫的completion
閉包中添加調用self.showMessage(index: 0)
:
UIView.animate(withDuration: 1.5, delay: 0.0, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options:[], animations: {
self.loginButton.bounds.size.width += 80.0
}, completion: { _ in
self.showMessage(index: 0)
})
複製代碼
動畫選項.transitionCurlDown
的效果,就像一張紙翻下來,看起來以下:
這種效果很好的讓靜態文本標籤的消息獲得用戶的關注。
注意:iPhone模擬器提供了慢動畫查看,方便看清那些比較快動畫的過程,Debug/Slow Animations(
Command + T
)。
添加一個狀態信息消除動畫方法:
func removeMessage(index: Int) {
UIView.animate(withDuration: 0.33, delay: 0.0, options: [], animations: {
self.status.center.x += self.view.frame.size.width
}) { (_) in
self.status.isHidden = true
self.status.center = self.statusPosition
self.showMessage(index: index+1)
}
}
複製代碼
這個信息消除方法在什麼地方調用呢?固然是狀態信息顯示結束後調用,所以在showMessage
方法的completion
閉包中添加:
delay(2.0) {
if index < self.messages.count-1 {
self.removeMessage(index: index)
} else {
//reset form
}
}
複製代碼
當「Connecting...」、「Authorizing」和「Failed」等幾個信息顯示完後,須要將信息標籤刪除和將登陸按鈕恢復原樣。
添加resetForm()
函數:
func resetForm() {
// 狀態信息標籤消失動畫
UIView.transition(with: status, duration: 0.2, options: .transitionFlipFromTop, animations: {
self.status.isHidden = true
self.status.center = self.statusPosition
}, completion: nil)
// 登陸按鈕和菊花轉恢復原來狀態的動畫
UIView.animate(withDuration: 0.2, delay: 0.0, options: [], animations: {
self.spinner.center = CGPoint(x: -20.0, y: 16.0)
self.spinner.alpha = 0.0
self.loginButton.backgroundColor = UIColor(red: 0.63, green: 0.84, blue: 0.35, alpha: 1.0)
self.loginButton.bounds.size.width -= 80.0
self.loginButton.center.y -= 60.0
}, completion: nil)
}
複製代碼
在以前的//reset form
處調用,resetForm()
。
結合以前的效果:
若是背景中的那些雲在屏幕上緩慢移動,並從左側移動到右側,而後到右側消失後再左側重新開始緩慢移動,那不是很酷嗎?(以前的gif能夠看到雲在移動,到目前爲止,雲只有透明度變化動畫,其實是由於我作GIF時項目已經完成了,GIF是我補作的,因此就。。😬)
添加一個animateCloud(cloud: UIImageView)
方法,代碼爲:
func animateCloud(cloud: UIImageView) {
// 假設雲從進入屏幕到離開屏幕須要大約60.0s,能夠計算出雲移動的速度
let cloudSpeed = view.frame.size.width / 60.0
// 雲的初始位置不必定是在座邊緣
let duration:CGFloat = (view.frame.size.width - cloud.frame.origin.x) / cloudSpeed
UIView.animate(withDuration: TimeInterval(duration), delay: 0.0, options: .curveLinear, animations: {
cloud.frame.origin.x = self.view.frame.size.width
}) { (_) in
cloud.frame.origin.x = -cloud.frame.size.width
self.animateCloud(cloud: cloud)
}
}
複製代碼
代碼解釋:
首先,計算☁️平均移動速度。假設雲從進入屏幕到離開屏幕須要大約60.0s(固然這個時間自定義)。
接下來,計算☁️移動到屏幕右側的持續時間。這邊要注意,☁️不是從屏幕的左邊緣開始,☁️移動的距離是view.frame.size.width - cloud.frame.origin.x
。
而後建立動畫方法animate(withDuration:delay:options:animations:completion:)
。這邊TimeInterval
是Double
別名,動畫選項使用.curveLinear
(不加速也不減速),這種狀況不多見,但做爲☁️的緩慢移動很是適合。
動畫閉包中cloud.frame.origin.x = self.view.frame.size.width
,就把☁️移動到屏幕右邊區域外。
到屏幕右區域外,當即在完成閉包中讓☁️到左邊緣外,cloud.frame.origin.x = -cloud.frame.size.width
。
最後不要忘記,把開始四個☁️的動畫,在viewDidAppear()
中添加:
animateCloud(cloud: cloud1)
animateCloud(cloud: cloud2)
animateCloud(cloud: cloud3)
animateCloud(cloud: cloud4)
複製代碼
總體效果:
本章是練習以前學習的動畫。
本章節的開始項目 Flight Info 是定時改變幾個視圖(幾個圖片和一個Label),代碼也很是簡單:
func changeFlight(to data: FlightData) {
// populate the UI with the next flight's data
summary.text = data.summary
flightNr.text = data.flightNr
gateNr.text = data.gateNr
departingFrom.text = data.departingFrom
arrivingTo.text = data.arrivingTo
flightStatus.text = data.flightStatus
bgImageView.image = UIImage(named: data.weatherImageName)
snowView.isHidden = !data.showWeatherEffects
// schedule next flight
delay(seconds: 3.0) {
self.changeFlight(to: data.isTakingOff ? parisToRome : londonToParis)
}
}
複製代碼
其中雪花❄️將在後面的章節26-粒子發射器學習,效果爲:
首先須要讓兩個背景圖像之間平滑過渡。 第一直覺多是簡單地淡出當前的圖像而後淡入新的圖像(透明度的變化)。 可是當alpha
接近零時,這種方法會顯示圖像背後的內容,效果看上去很差。以下所示:
在ViewController
中添加背景圖片淡入淡出的效果:
func fade(imageView: UIImageView, toImage: UIImage, showEffects: Bool) {
UIView.transition(with: imageView, duration: 1.0, options: .transitionCrossDissolve, animations: {
imageView.image = toImage
}, completion: nil)
UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseOut, animations: {
self.snowView.alpha = showEffects ? 1.0 : 0.0
}, completion: nil)
}
複製代碼
showEffects
參數表示顯示或隱藏降雪效果。
給changeFlight
方法添加一個是否有動畫的參數animated
,並更新changeFlight
方法:
func changeFlight(to data: FlightData, animated: Bool = false) {
summary.text = data.summary
flightNR.text = data.flightNr
gateNr.text = data.gateNr
departingFrom.text = data.departingFrom
arrivingTo.text = data.arrivingTo
flightStatus.text = data.flightStatus
if animated {
fade(imageView: bgImageView,
toImage: UIImage(named: data.weatherImageName)!,
showEffects: data.showWeatherEffects)
} else {
bgImageView.image = UIImage(named: data.weatherImageName)
snowView.isHidden = !data.showWeatherEffects
}
}
複製代碼
繼續在changeFlight
加一段讓背景圖不停循環變換的代碼:
delay(seconds: 3.0) {
self.changeFlight(to: data.isTakingOff ? parisToRome : londonToParis, animated: true)
}
複製代碼
如今的效果是:
對比開始時的效果,如今圖像之間過渡很是流暢,由於在背景圖淡入淡出的同時也對雪景效果進行了淡入淡出,動畫看起來很無縫。 你甚至能夠在羅馬看到它一瞬間下雪!😝😝
不知不覺掌握了一種重要的技術:過渡動畫可用於視圖的不可動畫屬性。(1-視圖動畫入門中的可用於動畫的屬性中沒有image
)
動畫選項.transitionCrossDissolve
很適合當前項目的效果,其它如.transitionFlipFromLeft
轉換就不大適合,能夠試試看。
僞裝3d轉換時文字背景顏色
這不是一個真正的3D效果,但它看起來很是接近。能夠經過輔助視圖來實現立體過渡動畫。 具體的方法是添加一個臨時Label,同時對這個兩個標籤的高度進行動畫,最後再刪除。
在ViewController
中添加一個枚舉:
enum AnimationDirection: Int {
case positive = 1
case negative = -1
}
複製代碼
這個枚舉的1和-1在以後表示在y軸變換時是向下仍是向上。
添加一個cubeTransition
方法:
func cubeTransition(label: UILabel, text: String, direction: AnimationDirection) {
let auxLabel = UILabel(frame: label.frame)
auxLabel.text = text
auxLabel.font = label.font
auxLabel.textAlignment = label.textAlignment
auxLabel.textColor = label.textColor
auxLabel.backgroundColor = label.backgroundColor
}
複製代碼
這是在構造一個臨時輔助的Label,把原來Label屬性複製給它,除了text
使用新的值。
在Y軸方向變換輔助Label,向cubeTransition
方法中添加:
let auxLabelOffset = CGFloat(direction.rawValue) * label.frame.size.height/2.0
auxLabel.transform = CGAffineTransform(translationX: 0.0, y: auxLabelOffset).scaledBy(x: 1.0, y: 0.1)
label.superview?.addSubview(auxLabel)
複製代碼
當單獨在Y軸縮放文本時,看起來就像一個豎着的平面被漸漸被推到,從而造成了假遠景效果(faux-perspective effect):
動畫代碼,繼續在cubeTransition
方法中添加:
UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseOut, animations: {
auxLabel.transform = .identity
// 本來的Label在Y軸上向反方向轉換
label.transform = CGAffineTransform(translationX: 0.0, y: -auxLabelOffset).scaledBy(x: 1.0, y: 0.1)
},completion: { _ in
// 把輔助Label的文本賦值給原來的Label,而後刪除輔助Label
label.text = auxLabel.text
label.transform = .identity
auxLabel.removeFromSuperview()
})
複製代碼
最後要在changeFlight
方法中添加這個假的3D轉動效果動畫:
if animated {
fade(imageView: bgImageView,
toImage: UIImage(named: data.weatherImageName)!,
showEffects: data.showWeatherEffects)
let direction: AnimationDirection = data.isTakingOff ?
.positive : .negative
cubeTransition(label: flightNr, text: data.flightNr, direction: direction)
cubeTransition(label: gateNr, text: data.gateNr, direction: direction)
} else {
// 不須要動畫
bgImageView.image = UIImage(named: data.weatherImageName)
snowView.isHidden = !data.showWeatherEffects
flightNr.text = data.flightNr
gateNr.text = data.gateNr
departingFrom.text = data.departingFrom
arrivingTo.text = data.arrivingTo
flightStatus.text = data.flightStatus
}
複製代碼
最終,航班號和入口號的Label轉換效果(我故意加長了動畫duration
,方便觀看):
爲啓程地和目的地Label添加淡入淡出和反彈的過渡(Fade and bounce transitions)動畫。
先添加方法moveLabel
,和上面的相似,建立一個輔助Label,並把原Label的一些屬性複製給它。
func moveLabel(label: UILabel, text: String, offset: CGPoint) {
let auxLabel = UILabel(frame: label.frame)
auxLabel.text = text
auxLabel.font = label.font
auxLabel.textAlignment = label.textAlignment
auxLabel.textColor = label.textColor
auxLabel.backgroundColor = .clear
auxLabel.transform = CGAffineTransform(translationX: offset.x, y: offset.y)
auxLabel.alpha = 0
view.addSubview(auxLabel)
}
複製代碼
爲原Label添加偏移轉換和透明度漸漸下降動畫,在moveLabel
方法裏添加:
UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseIn, animations: {
label.transform = CGAffineTransform(translationX: offset.x, y: offset.y)
label.alpha = 0.0
}, completion: nil)
複製代碼
爲輔助Label添加動畫,並在動畫結束後刪除,在moveLabel
方法裏添加:
UIView.animate(withDuration: 0.25, delay: 0.1, options: .curveEaseIn, animations: {
auxLabel.transform = .identity
auxLabel.alpha = 1.0
}, completion: { _ in
auxLabel.removeFromSuperview()
label.text = text
label.alpha = 1.0
label.transform = .identity
})
複製代碼
最後仍是在changeFlight
方法的if animated {
中添加:
// 啓程地和目的地Label動畫
let offsetDeparting = CGPoint(x: CGFloat(direction.rawValue * 80), y: 0.0)
moveLabel(label: departingFrom, text: data.departingFrom, offset: offsetDeparting)
let offsetArriving = CGPoint(x: 0.0, y: CGFloat(direction.rawValue * 50))
moveLabel(label: arrivingTo, text: data.arrivingTo, offset: offsetArriving)
複製代碼
啓程地和目的地Label動畫的方向能夠修改。
效果圖:
可使用前面的假的3D轉動效果動畫,changeFlight
的if animated {
中添加:
cubeTransition(label: flightStatus, text: data.flightStatus, direction: direction)
複製代碼
本章節最終的效果:
不少時候,須要多個連續的動畫。 前面的章節,已經使用動畫閉包和完成閉包包含兩個動畫效果。
這種方法適用於鏈接兩個簡單的動畫,可是當咱們想要將三個,四個或更多動畫組合在一塊兒時,就會致使一些使人難以置信的混亂和複雜的代碼。
讓咱們看看若是想將多個動畫連接在一塊兒並以矩形模式移動視圖,它會是什麼樣子:
假設實現以下效果:
爲了達到這個目的,能夠將幾個動畫和完成閉包連接起來:
UIView.animate(withDuration: 0.5,
animations: {
view.center.x += 200.0
},
completion: { _ in
UIView.animate(withDuration: 0.5,
animations: {
view.center.y += 100.0
},
completion: { _ in
UIView.animate(withDuration: 0.5,
animations: {
view.center.x -= 200.0
},
completion: { _ in
UIView.animate(withDuration: 0.5,
animations: {
view.center.y -= 100.0
}
)
}
)
}
)
}
)
```
複製代碼
看上去複雜繁瑣,這個時候就須要,使用本章節將要學習的關鍵幀動畫(Keyframe Animations),它能夠代替上面繁瑣的嵌套。
開始項目使用上一章節的完成項目Flight Info,經過讓✈️「飛機來」,學習關鍵幀動畫。
讓飛機✈️起飛能夠分紅四個不一樣階段的動畫(固然具體怎麼分能夠視狀況而定):
在跑道上移動
給✈️一點高度,向上傾斜飛行
給飛機更大的傾斜和更快的速度,向上傾斜加速飛行
最後10%時飛機漸漸淡出視圖
完整的動畫可能會讓人難以置信,但將動畫分解爲各個階段會使其更易於管理。 一旦爲每一個階段定義了關鍵幀,就會容易解決問題。
將讓飛機從起始位置起飛,繞圈,而後降落並滑行回到起點。 每次屏幕在航班背景之間切換時,都會運行此動畫。完整的動畫將看起來像這樣:
在ViewController
中添加planeDepart()
方法:
func planeDepart() {
let originalCenter = planeImage.center
UIView.animateKeyframes(withDuration: 1.5, delay: 0.0,
animations: {
//add keyframes
},
completion: nil
)
}
複製代碼
並在changeFlight
的 if animated {}
調用planeDepart()
:
if animated {
planeDepart()
...
複製代碼
添加第一個keyframe,在上面//add keyframes
添加:
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.25, animations: {
self.planeImage.center.x += 80.0
self.planeImage.center.y -= 10.0
})
複製代碼
addKeyframe(withRelativeStartTime:relativeDuration:animations:)
與以前動畫參數設置不一樣。withRelativeStartTime
和relativeDuration
都是相對時間百分比,相對於withDuration
。
使用相對值能夠指定keyframe
應該持續總時間的一小部分; UIKit獲取每一個keyframe
的相對持續時間,並自動計算每一個keyframe
的確切持續時間,爲咱們節省了大量工做。 上面的代碼的意思就是從1.5*0.0
開始,持續時間1.5*0.25
,✈️向右移動80.0,向上移動10.0。
接着上面,添加第二個keyframe:
UIView.addKeyframe(withRelativeStartTime: 0.1, relativeDuration: 0.4, animations: {
self.planeImage.transform = CGAffineTransform(rotationAngle: -.pi/8)
})
複製代碼
這一步是讓✈️有個向上傾斜的角度。
接着,添加第三個keyframe:
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25, animations: {
self.planeImage.center.x += 100.0
self.planeImage.center.y -= 50.0
self.planeImage.alpha = 0.0
})
複製代碼
這一步在移動同時逐漸消失。
添加第四個keyframe:
UIView.addKeyframe(withRelativeStartTime: 0.51, relativeDuration: 0.01, animations: {
self.planeImage.transform = .identity
self.planeImage.center = CGPoint(x: 0.0, y: originalCenter.y)
})
複製代碼
這一步讓✈️回到與原來高度相同的屏幕左邊緣,不過如今換處於透明度爲0狀態。
添加第五個keyframe:
UIView.addKeyframe(withRelativeStartTime: 0.55, relativeDuration: 0.45, animations: {
self.planeImage.alpha = 1.0
self.planeImage.center = originalCenter
})
複製代碼
讓飛機回到原來位置。
如今來看這個五個keyframe的開始時間,它們不是一個接着一個的,而是有交集的,這是由於分步動畫自己就有交叉,✈️在跑道上移動過程當中也會有向上移動,機頭也會漸漸向上傾斜,我把每一步的開始和持續時間列出來,獲得這個時間可能須要以前不停調節,看什麼時間分隔比較流暢🙂,下面是比較流暢的時間分隔方式。
(0.0, 0.25)
(0.1, 0.4)
(0.25, 0.25)
(0.51, 0.01)
(0.55, 0.45)
複製代碼
效果爲:
關鍵幀動畫不支持標準視圖動畫中可用的內置動畫緩動。 這是設計好的; 關鍵幀應該在特定時間開始和結束並相互流動。
若是上面動畫的每一個階段都有一個緩動曲線,那麼飛機就會抖動,而不是從一個動畫平穩地移動到下一個動畫。
上面沒有提到animateKeyframes(withDuration:delay:options:animations:completion:)
方法,這方法有一個options
參數(UIViewKeyframeAnimationOptions
),能夠提供幾種計算模式的選擇,每種模式提供了一種不一樣的方法來計算動畫的中間幀以及不一樣的優化器,以實現幀以前的轉化, 有關更多詳細信息,可查看文檔UIViewKeyframeAnimationOptions
。
因爲航班出發時間在屏幕頂部,變化時能夠簡單先向上移動到屏幕外,而後變化後再向下移動到屏幕內。
func summarySwitch(to summaryText: String) {
UIView.animateKeyframes(withDuration: 1.0, delay: 0.0, animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.45, animations: {
self.summary.center.y -= 100.0
})
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.45, animations: {
self.summary.center.y += 100.0
})
}, completion: nil)
delay(seconds: 0.5) {
self.summary.text = summaryText
}
}
複製代碼
一樣在changeFlight
的 if animated {}
中調用summarySwitch()
。
本章最後效果:
本文在個人我的博客中地址:系統學習iOS動畫之一:視圖動畫