上三篇關於UIView Animation的文章向你們介紹了基礎的UIView動畫,包括移動位置、改變大小、旋轉、彈簧動畫、過渡動畫。這些雖然看起來很簡單,可是若是咱們仔細分析、分解一個複雜動畫時,就會發現這些複雜的動畫實際上是由若干基礎的動畫組合而成的。今天這篇文章是實踐篇,我選擇了Raywenderlich Top 5 iOS 7 Animations這篇文章中的一個動畫效果,帶你們一塊兒實現。要實現這個動畫效果,除了用到咱們上三篇介紹過的知識點之外,還有兩個知識點在這篇會介紹給你們,咱們先看看實現的效果:ios
這個動畫示例實現的是一個展現航班信息的應用,左右滑動顯示不一樣的航班信息。咱們能夠分析一下都用到了哪些動畫:閉包
淡入淡出:起飛地和目的地、起飛地和目的地下面的橫線、底部的航班時間都使用了該動畫。app
位置移動:起飛地和目的地、小飛機都使用了該動畫。iview
旋轉:航站樓登機口前面的小箭頭、小飛機都使用了該動畫。動畫
過渡動畫: 背景圖片使用了淡入淡出效果的圖片替換過渡動畫。ui
僞3D動畫:頂部的時間、航班號、航站樓登機口信息、底部的起飛降落文字都是用了該動畫。spa
前三個動畫咱們以前已經介紹過了,如今咱們來介紹後兩個動畫。.net
這個僞3D的效果模擬的是一個立體長方形由一面翻轉到另外一面。由於這不是真正的3D效果,因此咱們能夠分析一下它是如何模擬的,以上面動畫中從下往上翻的效果爲例。首先顯示的是一個UILabel
,當開始進行翻轉時,當前顯示的UILabel
的高度開始慢慢變矮:翻譯
咱們看看用代碼怎麼實現:code
[cpp] view plaincopy
UIView.animateWithDuration(1, animations: {
self.devtalkingLabel.transform = CGAffineTransformMakeScale(1.0, 0.5)
})
咱們可使用一個轉換動畫,使用CGAffineTransformMakeScale
,它的第一個參數是x座標的比例,第二個參數是y座標的比例,這兩個值的範圍是1.0到0之間。上面的代碼用白話文翻譯出來就是在1秒內,devtalkingLabel
的寬度不變,高度減小一半,減小的過程會自動生成補間動畫。
咱們接着來分析,在UILabel
高度減小的同時,它的位置也會向上移動,咱們能夠用另一個轉換的動畫:
[cpp] view plaincopy
UIView.animateWithDuration(1, animations: {
self.devtalkingLabel.transform = CGAffineTransformMakeScale(1.0, 0.5)
self.devtalkingLabel.transform = CGAffineTransformMakeTranslation(1.0, -self.devtalkingLabel.frame.height / 2)
})
CGFffineTransformMakeTranslation
這個轉換動畫能夠移動UIView的位置,這裏須要注意它是以初始位置爲基礎進行移動的,因此上述代碼在字面上的意思是devtalkingLabel
在高度變小的同時向上移動它初始寬度一半的距離:
可是當咱們編譯運行後發現事與願違,轉換動畫不像動畫屬性動畫那樣能夠在animations
閉包中寫多個進行組合,而是由另外一個組合轉換動畫來實現:
[cpp] view plaincopy
UIView.animateWithDuration(1, animations: {
self.devtalkingLabel.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, 0.5), CGAffineTransformMakeTranslation(1.0, -self.devtalkingLabel.frame.height / 2))
self.devtalkingLabel.alpha = 0
})
來看看效果:
此時,3D翻轉效果的一個面已經成型了,也就是當前顯示的這一面被向上翻轉到頂部去了。接下來咱們要實現底部的面翻轉到當前顯示的這一面。很明顯這須要兩個面,但咱們只有一個UILabel
,因此在執行整個翻轉效果前須要先複製一個當前UILabel
:
[cpp] view plaincopy
let devtalkingLabelCopy = UILabel(frame: self.devtalkingLabel.frame)
devtalkingLabelCopy.alpha = 0
devtalkingLabelCopy.text = self.devtalkingLabel.text
devtalkingLabelCopy.font = self.devtalkingLabel.font
devtalkingLabelCopy.textAlignment = self.devtalkingLabel.textAlignment
devtalkingLabelCopy.textColor = self.devtalkingLabel.textColor
devtalkingLabelCopy.backgroundColor = UIColor.clearColor()
這樣咱們就複製了一個devtalkingLabel
,這個複製品將做爲底部的那一面,並且在一開始它的透明度是零,由於底面是看不到的。咱們能夠想象一下底面向上翻轉的效果,其實就是底面的高度從很小慢慢變大,位置從下慢慢向上移動,而後有一個淡入的效果,因此咱們在複製出devtalkingLabelCopy
後,要調整它的高度和位置,而後添加到父視圖中:
[cpp] view plaincopy
devtalkingLabelCopy.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, 0.1), CGAffineTransformMakeTranslation(1.0, self.devtalkingLabel.frame.height / 2))
self.view.addSubview(devtalkingLabelCopy)
上述代碼將devtalkingLabelCopy
的高度減少到本來的十分之一,位置向下移動半個高度的位置,而後在以前的animateWithDuration
方法的animations
閉包中添加以下兩行代碼:
[cpp] view plaincopy
devtalkingLabelCopy.alpha = 1
devtalkingLabelCopy.transform = CGAffineTransformIdentity
CGAffineTransformIdentity
的做用是將UIView
的transform
恢復到初始狀態,而後將透明度設爲1。編譯運行代碼咱們會看到devtalkingLabel
的高度會慢慢變小,位置慢慢上移,最後淡出,devtalkingLabelCopy
的高度慢慢變大,位置慢慢上移,最後淡入,整個效果看上去就像一個長方體在向上翻轉,達到3D的效果:
在要實現的動畫示例中,背景圖作了淡入淡出的圖片替換過渡動畫,這個動畫很簡單,咱們來看看這段僞代碼:
[cpp] view plaincopy
UIView.transitionWithView(backgroundImageView, duration: 2, options: .TransitionCrossDissolve, animations: {
backgroundImageView.image = UIImage(named: "imageName")
}, completion: nil)
這個方法在上一篇文章中已經介紹過,咱們只須要設置動畫選項爲.TransitionCrossDissolve
,在animations
閉包中給目標UIImageView
設置要過渡的圖片便可。
至此,示例動畫中用到的動畫知識點都向你們介紹過了,在這一節我會將示例動畫中主要的效果的僞代碼貼出來給你們說說。關於左右滑動的手勢以及PageControl
在這裏就不在累贅了。
爲了方便,咱們建立一個Flight.plist
文件做爲數據源:
咱們定義一個延遲加載的屬性flight
:
[cpp] view plaincopy
lazy var flight: NSArray = {
let path = NSBundle.mainBundle().pathForResource("Flight", ofType: "plist")
return NSArray(contentsOfFile: path!)!
}()
[cpp] view plaincopy
UIView.transitionWithView(self.backgroundImageView, duration: 2, options: .TransitionCrossDissolve, animations: {
self.backgroundImageView.image = UIImage(named: flightItem["bg"] as! String)
}, completion: nil)
上一節剛介紹過,只是這裏圖片名稱是從數據源中獲取的。
由於有3D翻轉動畫效果的UIView
比較多,並且有UILabel
也有UIImageView
,因此咱們能夠提煉成一個方法,將目標UIView
和數據源做爲參數:
[cpp] view plaincopy
func cubeAnimate(targetView: UIView, flightInfo: String) {
// 判斷UIView的具體實現類
if targetView.isKindOfClass(UILabel) {
let virtualTargetView = targetView as! UILabel
// 複製UIView,做爲底面
let viewCopy = UILabel(frame: virtualTargetView.frame)
viewCopy.alpha = 0
viewCopy.text = flightInfo
viewCopy.font = virtualTargetView.font
viewCopy.textAlignment = virtualTargetView.textAlignment
viewCopy.textColor = virtualTargetView.textColor
viewCopy.backgroundColor = UIColor.clearColor()
// 設置底面UIView的初始位置和高度
viewCopy.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, 0.1), CGAffineTransformMakeTranslation(1.0, viewCopy.frame.height / 2))
self.topView.addSubview(viewCopy)
UIView.animateWithDuration(2, animations: {
// 執行UIView和UIViewCopy的動畫
virtualTargetView.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, 0.1), CGAffineTransformMakeTranslation(1.0, -virtualTargetView.frame.height / 2))
virtualTargetView.alpha = 0
viewCopy.alpha = 1
viewCopy.transform = CGAffineTransformIdentity
}, completion: { _ in
// 當動畫執行完畢後,將UIViewCopy的信息賦值給UIView,並還原UIView的狀態,即與UIViewCopy相同的狀態,而後移除UIViewCopy
virtualTargetView.alpha = 1
virtualTargetView.text = viewCopy.text
virtualTargetView.transform = CGAffineTransformIdentity
viewCopy.removeFromSuperview()
})
} else if targetView.isKindOfClass(UIImageView) {
let virtualTargetView = targetView as! UIImageView
let viewCopy = UIImageView(frame: virtualTargetView.frame)
viewCopy.alpha = 0
viewCopy.image = UIImage(named: flightInfo)
viewCopy.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, 0.1), CGAffineTransformMakeTranslation(1.0, viewCopy.frame.height / 2))
self.topView.addSubview(viewCopy)
UIView.animateWithDuration(2, animations: {
virtualTargetView.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, 0.1), CGAffineTransformMakeTranslation(1.0, -virtualTargetView.frame.height / 2))
virtualTargetView.alpha = 0
viewCopy.alpha = 1
viewCopy.transform = CGAffineTransformIdentity
}, completion: { _ in
virtualTargetView.alpha = 1
virtualTargetView.image = viewCopy.image
virtualTargetView.transform = CGAffineTransformIdentity
viewCopy.removeFromSuperview()
})
}
}
具體有這麼幾個步驟:
判斷UIView
的具體實現類,判斷是UILabel
仍是UIImageView
。
複製一份UIView
,做爲底面。
設置UIViewCopy
的初始位置和高度。
執行UIView和
UIViewCopy`的動畫。
當動畫執行完畢後,將UIViewCopy
的信息賦值給UIView
,並還原UIView
的狀態,即與UIViewCopy
相同的狀態,而後移除UIViewCopy
。
由於航班信息有已降落和即將起飛兩種狀態,因此小箭頭旋轉涉及到一個方向問題,咱們能夠先定義一個枚舉類型:
[cpp] view plaincopy
enum RotateDirection: Int {
case Positive = 1
case Negative = -1
}
而後寫一個箭頭旋轉的方法:
[cpp] view plaincopy
func rotateAnimate(direction: Int) {
UIView.animateWithDuration(2, animations: {
// 判斷向上仍是向下旋轉
if RotateDirection.Positive.rawValue == direction {
// 在這個示例中小箭頭的初始狀態是飛機已降落狀態,因此想要箭頭從起飛狀態旋轉到降落狀態,只要恢復初始狀態便可
self.landedOrDepatureSmallArrowImageView.transform = CGAffineTransformIdentity
} else {
// 向上旋轉
let rotation = CGAffineTransformMakeRotation(CGFloat(RotateDirection.Negative.rawValue) * CGFloat(M_PI_2))
self.landedOrDepatureSmallArrowImageView.transform = rotation
}
}, completion: nil)
}
給你們解釋一下上述方法的幾個步驟:
首先判斷旋轉的方向,經過傳入的direction
參數。
若是判斷出是降落狀態的箭頭,也就是向下旋轉的箭頭,那麼咱們只須要將landedOrDepatureSmallArrowImageView
的transform
屬性恢復初始值便可,由於在這個示例中小箭頭的初始狀態就是飛機降落狀態。
向上旋轉時建立一個CGAffineTransformMakeRotation
,而後設置正確地方向和角度便可。
注:
CGAffineTransformMakeRotation
轉換每次都是以初始位置爲準,CGAffineTransformRotation
轉換是以每次的旋轉位置爲準。
起飛地、目的地、飛機的動畫是一個組合動畫,由於這裏面存在飛機出現和消失,以及旋轉的時機問題,咱們來看看這個方法:
[cpp] view plaincopy
func placeAndAirplaneA
刨析一下這個方法:
首先是起飛地向上移動同時淡出、目的地向下移動同時淡出、將飛機向右移出屏幕,這些動畫屬性的改變會產生補間動畫。
而後,當上面這些動畫結束後,根據數據源參數更改起飛地和目的地的值,同時將飛機移動屏幕左側外並向上旋轉一個角度,這些屬性的改變是不會產生補間動畫的,應爲它們在completion
閉包中。
最後再使用兩個動畫方法將起飛地向下移動,也就是恢復到初始位值同時淡入,將目的地向上移動,也就是恢復到初始位值同時淡入,將飛機移動到初始位置,將飛機的角度恢復到初始狀態。這裏爲何不把恢復飛機角度和恢復位置放在一個動畫方法裏呢?由於恢復飛機角度須要一個延遲時間,也就是當飛機飛入屏幕一會後再恢復角度,表示一個降落的效果,使動畫看起來更加逼真。
還有底部的時間、地點下地橫線都是簡單的淡入淡出動畫,這裏就再也不贅述了。
再簡單的動畫效果只要組合恰當,值設置得考究均可以作出出色的動畫效果,而簡單的動畫效果也是複雜動畫效果的基礎。上述動畫的示例代碼可能寫得不夠精細,還能夠提煉得更有層次,不過你們瞭解了知識點後能夠本身實現更考究的代碼結構,以及更精緻的動畫。