動畫的使用不只是讓你的App更為有特色,它也可以改善程式整體的使用者體驗。你可以看一下Apple如何在App端使用動畫來改善UX。舉例來說,在相片App,當你從相片集中選取一張照片,照片會展開出來,當你關掉後,它又會縮回去所選的相片集中。這讓你在瀏覽許多圖片時能夠正確的讓你知道你現在所處的位置為何。
Facebook的 Paper也應用很漂亮的動畫來提升App整體的使用者體驗。你選取某篇文章以向上翻轉起來的方式來閱讀。這個動作會將縮圖版本以反向展開,也就是如果將文章往下翻轉,便會縮回原來的縮圖。在這裏,動畫是用來表達App的運作方式,即使是App第一次的使用者,也能很快在不需要教學的狀況下,快速掌握它的使用方式,並找到它的導覽方式。
使用App動畫不只是加入不同使用者體驗,同時也可以讓你的使用者使用起來感動愉快與印象深刻進而會繼續使用它,避免解除App的安裝去尋找App store中更好的App。
在App中要加入動畫,有好幾種方式,有些是使用UIKit Dynamics、層動畫(layer animation)、視圖控制器轉換(view controller transition)或者使用第三方函式庫, 像是Facebook Pop library 或 JNWSpringAnimation 框架(framework).
在這篇教學文章,我們準備來看一下一些簡單的視圖動畫。 你可以 下載起始專案模板 ,模板是一個在表格視圖中列出教學中所呈現範例的Single View Application。我們將會使用這個來作為教學的示範範例,不用從頭開始。
這篇教學是一開始是重點介紹一些能夠讓視圖產生動畫的API,後面會在App中採用這些API用法來做示範。
基本視圖動畫
動畫的建立主要就是改變其屬性然後讓UIKit自動來產生動畫這樣而已。我們所變更的屬性是其中被標註為Animatable。
以下是Animatable屬性清單。
- center
- alpha
- frame
- bounds
- transform
- backgroundColor
- contentStretch
你會發現所有動畫至少會用到以上其中一項或多項屬性。
對於簡單的視圖動畫,UIKit提供了以下可以用來將視圖在畫面上動起來的API。
- UIView.animateWithDuration(_:, animations:)
- UIView.animateWithDuration(_:, animations:, completion:)
- UIView.animateWithDuration(_:, delay:, options:, animations:, completion:)
第一個有兩個參數–一個是動畫執行時間值,以秒數計,以及一個閉包,用來指定你想要變更的屬性。UIKit 將會接收原來的視圖狀態,並根據你在閉包中的指定,從初始狀態至結束狀態來建立一個平滑的轉換(transition,或者稱作過場)。
其他兩個API與第一個類似,但是它們有更多其他的參數,用來加入更多動畫設定。第二個的completion 閉包,可以讓你在動畫完成之後再指定另一個動畫,或者你可以做一些UI的清除。舉例來說,在另一個視圖已經以動畫進到場景後,從視圖階層(view hierarchy)移除原來的視圖。
第三個API有另外兩個參數- delay
,就是動畫開始之前所等待的時間,options
,是一個UIViewAnimationOptions常數,用來指示你要如何執行動畫的方式。以下是可以用的options變數。
彈性動畫(Spring Animations)
彈性動畫是模擬真實世界彈簧的行為模式,當一個視圖從一點移到另一點時,動畫會朝向設定終點,以彈跳或擺動的方式移動過去。
以下是我們所使用的彈性動畫方法。
- UIView.animateWithDuration(_:, delay:, usingSpringWithDamping:, initialSpringVelocity:, options:, animations:, completion:)
以上這個方法除了usingSpringWithDamping
與 initialSpringVelocity
這兩個新參數以外跟我們前面所看到的方法相似。 D Damping值為0~1,是用來判斷朝向動畫端點所需要的視圖彈跳數。其值越接近1,彈跳數會越少下。 而initialSpringVelocity
a如同名稱所述,是判斷動畫的初始速度。這判斷一開始的強烈性,倘若你想要一開始就很強勁的話,將值設定大一點,倘若你是想要平滑一點的動畫,你可以將值設為0。
關鍵格動畫(Keyframe Animations)
關鍵格動畫可以讓你設定不同階段的動畫。你可以將不同的動畫以一些共同的屬性群組在一起,不過還是可以做個別的控制。
除了只是將動畫沿著路徑移動以外,UIKit可以在執行不同階段的動畫。
關鍵格動畫的API如下。
- UIView.animateKeyframesWithDuration(_:, delay:, options:, animations:)
- UIView.addKeyframeWithRelativeStartTime(_:, relativeDuration:)
以上的方法是一起使用,第二嵌個套在第一個的animations閉包中。
第一個方法設定了全部的動畫設定,譬如要執行多久,延遲以及選項。然後你在動畫閉包中定義一個或多個的第二個方法(frame)來設定不同階段的動畫。
每一個frame的相對開始時間以及相對區間值是以0與1間的值來表示,其表示佔了整個動畫時間中多少百分比。
視圖轉換(View Transitions)
視圖轉換用於將你的新視圖加到你的視圖階層,或者從視圖階層移除。
建立視圖階層的API如下
- UIView.transitionWithView(_:, duration:, options:, animations:, completion:)
- UIView.transitionFromView(_:, toView:, duration:, options:, completion:)
使用第一個來導入一個視圖至視圖階層。這個方法用的參數與我們所見到的其他動畫方法類似。
第二個是用來將一個視圖從視圖階層取出然後將一個新視圖放在其位置。
範例
我們來看幾個在這個專案中讓視圖動起來的一些API範例。
範例 I
倘若你執行這個專案,你會見到一個表格視圖,上面有我即將介紹的一些範例列表。 在列表中選取Example I,你會見到一個App的登入畫面,有一個使用者名稱與密碼欄位以及登入按鈕。
我們想要App一開始的時後能夠以動畫方式進入畫面。
一開始,我們從視圖第一次出現時先隱藏這些視圖。在Auto Layout以前,用程式改變特定視圖的位置是一件簡單的事,但是自從我們在Storyboard檔案中設定Auto Layout後,我們必須在程式中變更約束條件,也就是會變更視圖的位置。
首先,我們需要取得我們想要變更約束條件的參照。打開Storyboard檔案。至Example I Scene的Constraints。
打開助理編輯器(Assistant Editor), 確認 ExampleIViewController.swift 會以另外一個分開的畫面出現在storyboard 的旁邊。將 Center X Alignment – View – Username 約束條件拖曳至 ExampleIViewController類別。建立一個名稱為 centerAlignUsername
的outlet。 針對 Center X Alignment – View – Password 執行同樣的動作,並設定其名稱為centerAlignPassword
。同樣的建立一個登入按鈕的 outlet ,稱作 loginButton 以及其對應的動作方法,稱作 login. 確認你設定UIButton其動作方法的型態為UIButton。程式如下所式。
@IBOutlet weak var centerAlignUsername: NSLayoutConstraint!
@IBOutlet weak var centerAlignPassword: NSLayoutConstraint!
@IBOutlet weak var loginButton: UIButton!
@IBAction func login(sender: UIButton) {
}
在 ExampleIViewController.swift加入以下的方法,這個方法在視圖呈現在畫面之前會被呼叫。
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
centerAlignUsername.constant -= view.bounds.width
centerAlignPassword.constant -= view.bounds.width
loginButton.alpha = 0.0
}
這可以讓使用者與密碼欄位離開視圖,並設定按鈕的alpha值為0,讓其為不可視。
加入以下的方法,在視圖出現之前呼叫它。
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
UIView.animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
self.centerAlignUsername.constant += self.view.bounds.width
self.centerAlignPassword.constant += self.view.bounds.width
self.loginButton.alpha = 1
self.view.layoutIfNeeded()
}, completion: nil)
}
這裏我們使用先前見到的 UIView.animateWithDuration()
方法。我們加入UIViewAnimationOptions.CurveEaseOut
功能,可以讓動畫一開始速度比較快,然後最後變慢。你可以在這裏實驗不同的功能。按住Command 鍵,然後點擊 UIViewAnimationOptions
,你可以見到所有可以用的功能。
這個動畫持續0.5 秒,並且馬上開始。你可以自己更改其時間,但是如果你設定App的動畫時間太久的話,對使用者來說感覺會不好。通常,動畫設定時間大約是0.5與0.7 秒,但是如我所說,這不是固定不變的,你可以決定你自認為最好的時間。
這個動畫與我們在viewWillAppear()
所做的相反。layoutIfNeeded()
是用來在視圖改變時立即佈局視圖。如果你沒有加進去的話,你無法見到它們以動畫方式進入畫面,而是只會顯示在它們在最後位置。執行 App,如下所示。
上面的動畫看起來比靜態呈現來得生動多了,不過這些視圖是同時間到達,感覺效果不是那麼好,修改以下的方法。
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
UIView.animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions.CurveEaseOut, animations: {
self.centerAlignUsername.constant += self.view.bounds.width
self.view.layoutIfNeeded()
}, completion: nil)
UIView.animateWithDuration(0.5, delay: 0.3, options: .CurveEaseOut, animations: {
self.centerAlignPassword.constant += self.view.bounds.width
self.view.layoutIfNeeded()
}, completion: nil)
UIView.animateWithDuration(0.5, delay: 0.4, options: .CurveEaseOut, animations: {
self.loginButton.alpha = 1
}, completion: nil)
}
執行App,結果視圖進到畫面的時間不同,在程式中,你可以見到我們依序設定不同的delay時間。
在登入畫面,如果登入失敗,通常會有個動畫用來指示使用者登入失敗。通常像是搖晃文字欄位或登入按鈕,並出現一個讓使用者知道登入不成功的訊息。我們將在登入按鈕使用彈性特效(spring effect),login()
函數如下。
@IBAction func login(sender: UIButton) {
let bounds = self.loginButton.bounds
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.2, initialSpringVelocity: 10, options: nil, animations: {
self.loginButton.bounds = CGRect(x: bounds.origin.x - 20, y: bounds.origin.y, width: bounds.size.width + 60, height: bounds.size.height)
self.loginButton.enabled = false
}, completion: nil)
}
以上是改變登入按鈕按下時的尺寸大小,以彈性動畫方式呈現按鈕的寬度會先展開,並回彈一點後回到之前的設定。
測試一下不同的damping值,倘若你設為1,按鈕便會展開,最終是沒有彈性的。你也可以在使用者名稱以及密碼欄位運用相同的方法。除了讓它們進入畫面後停止不動外,你也可以讓它們,像是彈簧一樣,在穩定下來之前,加上一點彈跳的動作。
範例 II
在你的App,可能有需要在一些動作發生後,自動替換視圖的背景圖片。你可以在視圖中馬上以另一張圖來取代,或者你可以採用淡入來建立一個很棒的轉換動作。我們會在這裡建立這個特效。打開ExampleIIViewController.swift 並加入以下的程式。
override func viewWillAppear(animated: Bool) {
let firstImageView = UIImageView(image: UIImage(named: "bg01.png"))
firstImageView.frame = view.frame
view.addSubview(firstImageView)
imageFadeIn(firstImageView)
}
func imageFadeIn(imageView: UIImageView) {
let secondImageView = UIImageView(image: UIImage(named: "bg02.png"))
secondImageView.frame = view.frame
secondImageView.alpha = 0.0
view.insertSubview(secondImageView, aboveSubview: imageView)
UIView.animateWithDuration(2.0, delay: 2.0, options: .CurveEaseOut, animations: {
secondImageView.alpha = 1.0
}, completion: {_ in
imageView.image = secondImageView.image
secondImageView.removeFromSuperview()
})
}
這裏我們建立個一個圖像視圖,並將其加入主視圖。接著我們呼叫imageFadeIn()
方法,以不同的圖片來建立第二視圖。我們將這個視圖加至圖像視圖上,並將alpha值設為0。在animation block中,我們將動畫的 alpha值設為可視。然後我們使用completion closure 來設定視圖的圖像為第二張圖片,並將第二個圖像視圖從視圖階層中移除,因為已經不需要它了。我已經加入了些延遲時間,所以在表格視圖選取Example II時動畫不會立即發生。這個時間有一點久,所以我們可以在範例中看到它的變化。
以下是它的特效。
範例 III
接下來我們來看關鍵格動畫。打開ExampleIIIViewController.swift 並加入以下的變數與函式到檔案中。我已經在程式中的每一個步驟做了註解。
var alertView: UIView!
func createView() {
//建立一個紅色視圖
let alertWidth: CGFloat = view.bounds.width
let alertHeight: CGFloat = view.bounds.height
let alertViewFrame: CGRect = CGRectMake(0, 0, alertWidth, alertHeight)
alertView = UIView(frame: alertViewFrame)
alertView.backgroundColor = UIColor.redColor()
//建立一個圖像視圖並加入至這個視圖
let imageView = UIImageView(frame: CGRectMake(0, 0, alertWidth, alertHeight/2))
imageView.image = UIImage(named: "bike_traveler.png")
alertView.addSubview(imageView)
//建立一個按鈕,並為按鈕被按下的動作來設定一個監聽者(listener)。然後這按鈕被加到alert view上。
let button = UIButton.buttonWithType(UIButtonType.System) as UIButton
button.setTitle("Dismiss", forState: UIControlState.Normal)
button.backgroundColor = UIColor.whiteColor()
let buttonWidth: CGFloat = alertWidth/2
let buttonHeight: CGFloat = 40
button.frame = CGRectMake(alertView.center.x - buttonWidth/2, alertView.center.y - buttonHeight/2, buttonWidth, buttonHeight)
button.addTarget(self, action: Selector("dismissAlert"), forControlEvents: UIControlEvents.TouchUpInside)
alertView.addSubview(button)
view.addSubview(alertView)
}
func dismissAlert() {
}
In viewDidLoad()
call the createView()
function. Add the following to the bottom of viewDidLoad().
createView()
執行這個App並從表格視圖選取Example III,你會見到一個紅色視圖加上一張圖像視圖在其上方,還有一個按鈕在中間。
我們想要這個視圖(現在開始我所指的是提示視圖)將尺寸縮減然後離開視圖。
當我們建立這個按鈕時,我們加入一個監聽者給它。當按鈕按下時, dismissAlert()
便會被呼叫。修改函數如下所示。
func dismissAlert() {
let bounds = alertView.bounds
let smallFrame = CGRectInset(alertView.frame, alertView.frame.size.width / 4, alertView.frame.size.height / 4)
let finalFrame = CGRectOffset(smallFrame, 0, bounds.size.height)
UIView.animateKeyframesWithDuration(4, delay: 0, options: .CalculationModeCubic, animations: {
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.5) {
self.alertView.frame = smallFrame
}
UIView.addKeyframeWithRelativeStartTime(0.5, relativeDuration: 0.5) {
self.alertView.frame = finalFrame
}
}, completion: nil)
}
在上面的程式中,我們建立了畫格(frame),呈現了我們視圖動畫的兩階段。smallFrame 將alertView縮為一半,中心點不變,而finalFrame 的位置是位於畫面的底部,並離開視圖。
我們在Keyframe動畫中使用兩個關鍵畫格。首先設定alertView的畫格為smallframe,而第二個設為finalframe。結果是alertView會縮為一半大小然後向下掉落離開視圖。注意我有設定較長的動畫時間為4秒,你可以改變它,因為我只是想讓這個動畫以慢動作的方式來示範而已。執行這個App並選取Example III。
這個動畫與我們所預期的有點不同。你可以看到紅色的alertView動畫是正常的,但是它的子畫格部分沒有改變。變更父畫格部分,而沒有自動變更其子畫格。
我們使用iOS7所導入的功能,稱作UIView snapshots來修復這個動畫。這可以讓你將UIView 以及它的階層(hierarchy)一起做快照(sanpshot),然後重繪(render)至新的UIView中。
在 dismissAlert()
的keyframe 動畫程式之前加入以下的程式。
let snapshot = alertView.snapshotViewAfterScreenUpdates(false)
snapshot.frame = alertView.frame
view.addSubview(snapshot)
alertView.removeFromSuperview()
這裏我們建立一個snapshot視圖,並加入其主視圖。然後我們從視圖中移除alertView,因為快照會取代它。
將下面的關鍵格動畫以下面的程式來替代。
UIView.animateKeyframesWithDuration(4, delay: 0, options: .CalculationModeCubic, animations: {
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.5) {
snapshot.frame = smallFrame
}
UIView.addKeyframeWithRelativeStartTime(0.5, relativeDuration: 0.5) {
snapshot.frame = finalFrame
}
}, completion: nil)
執行這個應用程式並按下Dismiss,這個視圖(真的是快照)就能產生如預期般的動畫了。
範例 IV
我們已經看過如何讓視圖動起來,在這的例子中,我們來看看如何讓表格視圖也動起來。
倘若你從表格視圖選取Example IV,你將會發現另一個有些項目列表在內的表格視圖。在表格視圖出現後,這些列表的項目已經放進表格中。我們要將其動起來,產生更有趣的特效。
打開ExampleIVViewController.swift,並加入以下的方法。
override func viewWillAppear(animated: Bool) {
animateTable()
}
func animateTable() {
tableView.reloadData()
let cells = tableView.visibleCells()
let tableHeight: CGFloat = tableView.bounds.size.height
for i in cells {
let cell: UITableViewCell = i as UITableViewCell
cell.transform = CGAffineTransformMakeTranslation(0, tableHeight)
}
var index = 0
for a in cells {
let cell: UITableViewCell = a as UITableViewCell
UIView.animateWithDuration(1.5, delay: 0.05 * Double(index), usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: nil, animations: {
cell.transform = CGAffineTransformMakeTranslation(0, 0);
}, completion: nil)
index += 1
}
}
這裏,在視圖出現時, animateTable()
函數會被呼叫。我們重新載入表格視圖資料,然後以迴圈將每一個現在畫面上可見的cell移到畫面底部。然後我們重複將所有我們移到畫面底部的cell以彈性動畫方式移回原位。
執行App,你可以見到以下的效果。
總結
我們見到了多種的API ,讓你可以用來在App的視圖中加入動畫。這不是iOS動畫全面的指南,但是透過這些簡單API的使用,你可以在你的App中建立各式的動畫。要進一步深入iOS動畫,你可以看一下UIKit Dynamics、 層動畫(layer animation)、視圖控制器轉換(view controller transition),甚至更多動作特效。你也可以參考第三方函式庫像是Facebook Pop 與 JNWSpringAnimation框架,這些可以讓你更容易的建立精緻複雜的動畫。
你可以在這裡下載完整的專案.
如同往常,請留下你的建議與有關這篇教學內容的想法。
原文:http://www.appcoda.com/view-animation-in-swift/