系統學習iOS動畫之一:視圖動畫

本文是我學習《iOS Animations by Tutorials》 筆記中的一篇。 文中詳細代碼都放在個人Github上 andyRon/LearniOSAnimationshtml

這個部分介紹UIKit動畫API,這些API專門用於輕鬆製做視圖動畫(View Animations),同時避免核心動畫(Core Animation)(見系統學習iOS動畫之三:圖層動畫)的複雜性。ios

UIKit動畫API不只易於使用,並且提供了大量靈活性和強大功能,能夠處理大多數(固然不是所有)動畫要求。git

UIKit動畫API能夠在屏幕上爲最終繼承自UIView的任何對象設置動畫,例如:UILabelUIImageViewUIButton等等,也能夠是本身建立的任何自定義最終繼承自UIView類。github

本文包括五個章節,完成兩個項目BahamaAirLoginScreenFlight Infoswift

BahamaAirLoginScreen 是一個登陸頁面項目,一、二、3章節爲這個項目的一些UI添加各類動畫。數組

1-視圖動畫入門 —— 學習如何移動,縮放和淡化視圖等基本的UIKit API。
2-彈簧動畫 —— 在線性動畫的概念基礎上,使用彈簧動畫創造出更引人注目的效果。😊
3-過渡動畫 —— 視圖的出現和消失。bash

Flight Info 是一個航班狀態變化項目,四、5章節用一些高級一點動畫來完成這個項目。網絡

4-練習視圖動畫 —— 練習前面學到的動畫技術。
5-關鍵幀動畫 —— 使用關鍵幀動畫來建立由許多不一樣階段組成的複雜動畫。閉包

1-視圖動畫入門

第一個動畫

開始項目 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 :動畫開始以前的延遲時間。

optionsUIView.AnimationOptions的數組,用來定義動畫的變化形式,以後會詳細說明。

animations :提供動畫的閉包,也就是動畫代碼。

completion :動畫執行完成後的閉包 。

還有 usingSpringWithDampinginitialSpringVelocity以後章節會提到。

可動畫屬性

前面,使用center建立簡單的位置變化視圖動畫。

並不是全部視圖屬性均可以設置動畫,但全部視圖動畫(從最簡單到最複雜)均可以經過動畫視圖上的屬性來構建。下面來看看可用於動畫的屬性有哪些:

位置的大小

bounds frame center

外形(Appearance)

backgroundColor
alpha : 可建立淡入和淡出效果。

轉換(Transformation)

transform : 設置視圖的旋轉,縮放和/或位置的動畫。

image-20181116174323974

這些看起來像是很是基本的動畫,能夠製做使人驚訝的複雜動畫效果!😉

動畫選項

動畫選項(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)
複製代碼

2-彈簧動畫

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)
複製代碼

效果爲:

3-過渡動畫

過渡動畫(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
  )
}
複製代碼

效果:

transitionFlipFromBottomtransitionFlipFromLeft替代後的效果:

完整的預約義過渡動畫的選項以下,這些動畫選項和上兩節中出現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)
    }
}
複製代碼

代碼解釋:

  1. 首先,計算☁️平均移動速度。假設雲從進入屏幕到離開屏幕須要大約60.0s(固然這個時間自定義)

  2. 接下來,計算☁️移動到屏幕右側的持續時間。這邊要注意,☁️不是從屏幕的左邊緣開始,☁️移動的距離是view.frame.size.width - cloud.frame.origin.x

  3. 而後建立動畫方法animate(withDuration:delay:options:animations:completion:)。這邊TimeIntervalDouble別名,動畫選項使用.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)
複製代碼

總體效果:

總體效果

4-練習視圖動畫

本章是練習以前學習的動畫。

本章節的開始項目 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-粒子發射器學習,效果爲:

開始項目圖示

淡出淡入動畫(Crossfading animations)

首先須要讓兩個背景圖像之間平滑過渡。 第一直覺多是簡單地淡出當前的圖像而後淡入新的圖像(透明度的變化)。 可是當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轉換就不大適合,能夠試試看。

立體過渡(Cube transitions)

僞裝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轉動效果動畫,changeFlightif animated {中添加:

cubeTransition(label: flightStatus, text: data.flightStatus, direction: direction)

複製代碼

本章節最終的效果:

5-關鍵幀動畫

不少時候,須要多個連續的動畫。 前面的章節,已經使用動畫閉包和完成閉包包含兩個動畫效果。

這種方法適用於鏈接兩個簡單的動畫,可是當咱們想要將三個,四個或更多動畫組合在一塊兒時,就會致使一些使人難以置信的混亂和複雜的代碼。

讓咱們看看若是想將多個動畫連接在一塊兒並以矩形模式移動視圖,它會是什麼樣子:

假設實現以下效果:

爲了達到這個目的,能夠將幾個動畫和完成閉包連接起來:

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,經過讓✈️「飛機來」,學習關鍵幀動畫。

image-20181013172100870

讓飛機✈️起飛能夠分紅四個不一樣階段的動畫(固然具體怎麼分能夠視狀況而定):

  1. 在跑道上移動

  2. 給✈️一點高度,向上傾斜飛行

  3. 給飛機更大的傾斜和更快的速度,向上傾斜加速飛行

  4. 最後10%時飛機漸漸淡出視圖

完整的動畫可能會讓人難以置信,但將動畫分解爲各個階段會使其更易於管理。 一旦爲每一個階段定義了關鍵幀,就會容易解決問題。

設置關鍵幀動畫

將讓飛機從起始位置起飛,繞圈,而後降落並滑行回到起點。 每次屏幕在航班背景之間切換時,都會運行此動畫。完整的動畫將看起來像這樣:

ViewController中添加planeDepart()方法:

func planeDepart() {
  let originalCenter = planeImage.center

  UIView.animateKeyframes(withDuration: 1.5, delay: 0.0,
    animations: {
      //add keyframes
    }, 
    completion: nil
  )
} 
複製代碼

並在changeFlightif 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:) 與以前動畫參數設置不一樣。withRelativeStartTimerelativeDuration都是相對時間百分比,相對於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
    }
}
複製代碼

一樣在changeFlightif animated {}中調用summarySwitch()

本章最後效果:

本文在個人我的博客中地址:系統學習iOS動畫之一:視圖動畫

相關文章
相關標籤/搜索