iOS UIView動畫實踐(四):過渡與僞3D動畫

前言

上三篇關於UIView Animation的文章向你們介紹了基礎的UIView動畫,包括移動位置、改變大小、旋轉、彈簧動畫、過渡動畫。這些雖然看起來很簡單,可是若是咱們仔細分析、分解一個複雜動畫時,就會發現這些複雜的動畫實際上是由若干基礎的動畫組合而成的。今天這篇文章是實踐篇,我選擇了Raywenderlich Top 5 iOS 7 Animations這篇文章中的一個動畫效果,帶你們一塊兒實現。要實現這個動畫效果,除了用到咱們上三篇介紹過的知識點之外,還有兩個知識點在這篇會介紹給你們,咱們先看看實現的效果:ios

這個動畫示例實現的是一個展現航班信息的應用,左右滑動顯示不一樣的航班信息。咱們能夠分析一下都用到了哪些動畫:閉包

  • 淡入淡出:起飛地和目的地、起飛地和目的地下面的橫線、底部的航班時間都使用了該動畫。app

  • 位置移動:起飛地和目的地、小飛機都使用了該動畫。iview

  • 旋轉:航站樓登機口前面的小箭頭、小飛機都使用了該動畫。動畫

  • 過渡動畫: 背景圖片使用了淡入淡出效果的圖片替換過渡動畫。ui

  • 僞3D動畫:頂部的時間、航班號、航站樓登機口信息、底部的起飛降落文字都是用了該動畫。spa

前三個動畫咱們以前已經介紹過了,如今咱們來介紹後兩個動畫。.net

僞3D動畫效果

這個僞3D的效果模擬的是一個立體長方形由一面翻轉到另外一面。由於這不是真正的3D效果,因此咱們能夠分析一下它是如何模擬的,以上面動畫中從下往上翻的效果爲例。首先顯示的是一個UILabel,當開始進行翻轉時,當前顯示的UILabel的高度開始慢慢變矮:翻譯

咱們看看用代碼怎麼實現:code

[cpp] view plaincopy

  1. UIView.animateWithDuration(1, animations: {  

  2.     self.devtalkingLabel.transform = CGAffineTransformMakeScale(1.0, 0.5)  

  3. })  

咱們可使用一個轉換動畫,使用CGAffineTransformMakeScale,它的第一個參數是x座標的比例,第二個參數是y座標的比例,這兩個值的範圍是1.0到0之間。上面的代碼用白話文翻譯出來就是在1秒內,devtalkingLabel的寬度不變,高度減小一半,減小的過程會自動生成補間動畫。

咱們接着來分析,在UILabel高度減小的同時,它的位置也會向上移動,咱們能夠用另一個轉換的動畫:

[cpp] view plaincopy

  1. UIView.animateWithDuration(1, animations: {  

  2.     self.devtalkingLabel.transform = CGAffineTransformMakeScale(1.0, 0.5)  

  3.     self.devtalkingLabel.transform = CGAffineTransformMakeTranslation(1.0, -self.devtalkingLabel.frame.height / 2)  

  4. })  

CGFffineTransformMakeTranslation這個轉換動畫能夠移動UIView的位置,這裏須要注意它是以初始位置爲基礎進行移動的,因此上述代碼在字面上的意思是devtalkingLabel在高度變小的同時向上移動它初始寬度一半的距離:

可是當咱們編譯運行後發現事與願違,轉換動畫不像動畫屬性動畫那樣能夠在animations閉包中寫多個進行組合,而是由另外一個組合轉換動畫來實現:

[cpp] view plaincopy

  1. UIView.animateWithDuration(1, animations: {  

  2.     self.devtalkingLabel.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, 0.5), CGAffineTransformMakeTranslation(1.0, -self.devtalkingLabel.frame.height / 2))  

  3.     self.devtalkingLabel.alpha = 0  

  4. })  

來看看效果:

此時,3D翻轉效果的一個面已經成型了,也就是當前顯示的這一面被向上翻轉到頂部去了。接下來咱們要實現底部的面翻轉到當前顯示的這一面。很明顯這須要兩個面,但咱們只有一個UILabel,因此在執行整個翻轉效果前須要先複製一個當前UILabel

[cpp] view plaincopy

  1. let devtalkingLabelCopy = UILabel(frame: self.devtalkingLabel.frame)  

  2. devtalkingLabelCopy.alpha = 0  

  3. devtalkingLabelCopy.text = self.devtalkingLabel.text  

  4. devtalkingLabelCopy.font = self.devtalkingLabel.font  

  5. devtalkingLabelCopy.textAlignment = self.devtalkingLabel.textAlignment  

  6. devtalkingLabelCopy.textColor = self.devtalkingLabel.textColor  

  7. devtalkingLabelCopy.backgroundColor = UIColor.clearColor()  

這樣咱們就複製了一個devtalkingLabel,這個複製品將做爲底部的那一面,並且在一開始它的透明度是零,由於底面是看不到的。咱們能夠想象一下底面向上翻轉的效果,其實就是底面的高度從很小慢慢變大,位置從下慢慢向上移動,而後有一個淡入的效果,因此咱們在複製出devtalkingLabelCopy後,要調整它的高度和位置,而後添加到父視圖中:

[cpp] view plaincopy

  1. devtalkingLabelCopy.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, 0.1), CGAffineTransformMakeTranslation(1.0, self.devtalkingLabel.frame.height / 2))  

  2. self.view.addSubview(devtalkingLabelCopy)  

上述代碼將devtalkingLabelCopy的高度減少到本來的十分之一,位置向下移動半個高度的位置,而後在以前的animateWithDuration方法的animations閉包中添加以下兩行代碼:

[cpp] view plaincopy

  1. devtalkingLabelCopy.alpha = 1  

  2. devtalkingLabelCopy.transform = CGAffineTransformIdentity  

CGAffineTransformIdentity的做用是將UIViewtransform恢復到初始狀態,而後將透明度設爲1。編譯運行代碼咱們會看到devtalkingLabel的高度會慢慢變小,位置慢慢上移,最後淡出,devtalkingLabelCopy的高度慢慢變大,位置慢慢上移,最後淡入,整個效果看上去就像一個長方體在向上翻轉,達到3D的效果:

替換UIView過渡動畫

在要實現的動畫示例中,背景圖作了淡入淡出的圖片替換過渡動畫,這個動畫很簡單,咱們來看看這段僞代碼:

[cpp] view plaincopy

  1. UIView.transitionWithView(backgroundImageView, duration: 2, options: .TransitionCrossDissolve, animations: {  

  2.     backgroundImageView.image = UIImage(named: "imageName")  

  3. }, completion: nil)  

這個方法在上一篇文章中已經介紹過,咱們只須要設置動畫選項爲.TransitionCrossDissolve,在animations閉包中給目標UIImageView設置要過渡的圖片便可。

示例動畫

至此,示例動畫中用到的動畫知識點都向你們介紹過了,在這一節我會將示例動畫中主要的效果的僞代碼貼出來給你們說說。關於左右滑動的手勢以及PageControl在這裏就不在累贅了。

數據源

爲了方便,咱們建立一個Flight.plist文件做爲數據源:

咱們定義一個延遲加載的屬性flight

[cpp] view plaincopy

  1. lazy var flight: NSArray = {  

  2.     let path = NSBundle.mainBundle().pathForResource("Flight", ofType: "plist")  

  3.     return NSArray(contentsOfFile: path!)!  

  4. }()  

背景圖片過渡

[cpp] view plaincopy

  1. UIView.transitionWithView(self.backgroundImageView, duration: 2, options: .TransitionCrossDissolve, animations: {  

  2.     self.backgroundImageView.image = UIImage(named: flightItem["bg"] as! String)  

  3. }, completion: nil)  

上一節剛介紹過,只是這裏圖片名稱是從數據源中獲取的。

3D翻轉

由於有3D翻轉動畫效果的UIView比較多,並且有UILabel也有UIImageView,因此咱們能夠提煉成一個方法,將目標UIView和數據源做爲參數:

[cpp] view plaincopy

  1. func cubeAnimate(targetView: UIView, flightInfo: String) {  

  2.     // 判斷UIView的具體實現類  

  3.     if targetView.isKindOfClass(UILabel) {  

  4.         let virtualTargetView = targetView as! UILabel  

  5.         // 複製UIView,做爲底面  

  6.         let viewCopy = UILabel(frame: virtualTargetView.frame)  

  7.         viewCopy.alpha = 0  

  8.         viewCopy.text = flightInfo  

  9.         viewCopy.font = virtualTargetView.font  

  10.         viewCopy.textAlignment = virtualTargetView.textAlignment  

  11.         viewCopy.textColor = virtualTargetView.textColor  

  12.         viewCopy.backgroundColor = UIColor.clearColor()  

  13.         // 設置底面UIView的初始位置和高度  

  14.         viewCopy.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, 0.1), CGAffineTransformMakeTranslation(1.0, viewCopy.frame.height / 2))  

  15.         self.topView.addSubview(viewCopy)  

  16.         UIView.animateWithDuration(2, animations: {  

  17.             // 執行UIView和UIViewCopy的動畫  

  18.             virtualTargetView.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, 0.1), CGAffineTransformMakeTranslation(1.0, -virtualTargetView.frame.height / 2))  

  19.             virtualTargetView.alpha = 0  

  20.             viewCopy.alpha = 1  

  21.             viewCopy.transform = CGAffineTransformIdentity  

  22.         }, completion: { _ in  

  23.             // 當動畫執行完畢後,將UIViewCopy的信息賦值給UIView,並還原UIView的狀態,即與UIViewCopy相同的狀態,而後移除UIViewCopy  

  24.             virtualTargetView.alpha = 1  

  25.             virtualTargetView.text = viewCopy.text  

  26.             virtualTargetView.transform = CGAffineTransformIdentity  

  27.             viewCopy.removeFromSuperview()  

  28.         })  

  29.      } else if targetView.isKindOfClass(UIImageView) {  

  30.          let virtualTargetView = targetView as! UIImageView  

  31.          let viewCopy = UIImageView(frame: virtualTargetView.frame)  

  32.          viewCopy.alpha = 0  

  33.          viewCopy.image = UIImage(named: flightInfo)  

  34.          viewCopy.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, 0.1), CGAffineTransformMakeTranslation(1.0, viewCopy.frame.height / 2))  

  35.          self.topView.addSubview(viewCopy)  

  36.          UIView.animateWithDuration(2, animations: {  

  37.              virtualTargetView.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(1.0, 0.1), CGAffineTransformMakeTranslation(1.0, -virtualTargetView.frame.height / 2))  

  38.              virtualTargetView.alpha = 0  

  39.              viewCopy.alpha = 1  

  40.              viewCopy.transform = CGAffineTransformIdentity  

  41.          }, completion: { _ in  

  42.              virtualTargetView.alpha = 1  

  43.              virtualTargetView.image = viewCopy.image  

  44.              virtualTargetView.transform = CGAffineTransformIdentity  

  45.              viewCopy.removeFromSuperview()  

  46.          })  

  47.      }  

  48. }  

具體有這麼幾個步驟:

  • 判斷UIView的具體實現類,判斷是UILabel仍是UIImageView

  • 複製一份UIView,做爲底面。

  • 設置UIViewCopy的初始位置和高度。

  • 執行UIViewUIViewCopy`的動畫。

  • 當動畫執行完畢後,將UIViewCopy的信息賦值給UIView,並還原UIView的狀態,即與UIViewCopy相同的狀態,而後移除UIViewCopy

小箭頭旋轉動畫

由於航班信息有已降落和即將起飛兩種狀態,因此小箭頭旋轉涉及到一個方向問題,咱們能夠先定義一個枚舉類型:

[cpp] view plaincopy

  1. enum RotateDirection: Int {  

  2.     case Positive = 1  

  3.     case Negative = -1  

  4. }  

而後寫一個箭頭旋轉的方法:

[cpp] view plaincopy

  1. func rotateAnimate(direction: Int) {  

  2.    UIView.animateWithDuration(2, animations: {  

  3.        // 判斷向上仍是向下旋轉  

  4.        if RotateDirection.Positive.rawValue == direction {  

  5.            // 在這個示例中小箭頭的初始狀態是飛機已降落狀態,因此想要箭頭從起飛狀態旋轉到降落狀態,只要恢復初始狀態便可  

  6.            self.landedOrDepatureSmallArrowImageView.transform = CGAffineTransformIdentity  

  7.        } else {  

  8.            // 向上旋轉  

  9.            let rotation = CGAffineTransformMakeRotation(CGFloat(RotateDirection.Negative.rawValue) * CGFloat(M_PI_2))  

  10.            self.landedOrDepatureSmallArrowImageView.transform = rotation  

  11.        }  

  12.     }, completion: nil)  

  13. }  

給你們解釋一下上述方法的幾個步驟:

  • 首先判斷旋轉的方向,經過傳入的direction參數。

  • 若是判斷出是降落狀態的箭頭,也就是向下旋轉的箭頭,那麼咱們只須要將landedOrDepatureSmallArrowImageViewtransform屬性恢復初始值便可,由於在這個示例中小箭頭的初始狀態就是飛機降落狀態。

  • 向上旋轉時建立一個CGAffineTransformMakeRotation,而後設置正確地方向和角度便可。

注:CGAffineTransformMakeRotation轉換每次都是以初始位置爲準,CGAffineTransformRotation轉換是以每次的旋轉位置爲準。

地點和飛機動畫

起飛地、目的地、飛機的動畫是一個組合動畫,由於這裏面存在飛機出現和消失,以及旋轉的時機問題,咱們來看看這個方法:

[cpp] view plaincopy

  1. func placeAndAirplaneA

刨析一下這個方法:

  • 首先是起飛地向上移動同時淡出、目的地向下移動同時淡出、將飛機向右移出屏幕,這些動畫屬性的改變會產生補間動畫。

  • 而後,當上面這些動畫結束後,根據數據源參數更改起飛地和目的地的值,同時將飛機移動屏幕左側外並向上旋轉一個角度,這些屬性的改變是不會產生補間動畫的,應爲它們在completion閉包中。

  • 最後再使用兩個動畫方法將起飛地向下移動,也就是恢復到初始位值同時淡入,將目的地向上移動,也就是恢復到初始位值同時淡入,將飛機移動到初始位置,將飛機的角度恢復到初始狀態。這裏爲何不把恢復飛機角度和恢復位置放在一個動畫方法裏呢?由於恢復飛機角度須要一個延遲時間,也就是當飛機飛入屏幕一會後再恢復角度,表示一個降落的效果,使動畫看起來更加逼真。

還有底部的時間、地點下地橫線都是簡單的淡入淡出動畫,這裏就再也不贅述了。

結束語

再簡單的動畫效果只要組合恰當,值設置得考究均可以作出出色的動畫效果,而簡單的動畫效果也是複雜動畫效果的基礎。上述動畫的示例代碼可能寫得不夠精細,還能夠提煉得更有層次,不過你們瞭解了知識點後能夠本身實現更考究的代碼結構,以及更精緻的動畫。

相關文章
相關標籤/搜索