你曾在 Keynote 使用過瞬間移動動畫( magic move animation )嗎?藉由瞬間移動效果, 你可以輕鬆建立投影片間的平滑動畫( slick animation )。Keynote 會自動分析兩張投影片之間的物件,並自動渲染動畫。同樣,SwiftUI 也將瞬間移動動畫帶入應用程式開發中, 使用該框架的動畫是自動且神奇的。當你定義一個視圖的兩個狀態,SwiftUI 會找出其餘的狀態,接著以動畫呈現兩個狀態之間的變化。
SwiftUI 使你能夠為單個視圖的變化以及兩個視圖之間的轉場來設定動畫。SwiftUI 框架具有許多的內建動畫,可建立不同的效果。
SwiftUI 提供兩種動畫類型:隱式( implicit )與顯式( explicit )。這兩個方式可以讓你為視圖及視圖轉場設定動畫。為了實作隱式動畫,SwiftUI 框架提供一個名為 animation
的修飾器,你把這個修飾器加到要設定動畫的視圖上,並指定喜歡的動畫類型。或者,你可以定義動畫的持續時間與延遲時間,SwiftUI 會根據視圖的狀態變化來自動渲染動畫。
顯式動畫對你要顯示的動畫提供更具侷限性的控制,其並非將修飾器加到視圖,而是在 withAnimation()
區塊中,告訴 SwiftUI 若有什麼的狀態變化時,要繪製動畫。
仍是覺得有些困惑嗎?沒有關係,藉由幾個範例,你就會更有概念了。
我們從隱式動畫來開始介紹,我建議你建立一個新專案來看動畫的實際效果。你可以任意為專案命名,而我將它命名為 SwiftUIAnimation
。
我們來看圖 9.1,這是一個簡單的可點擊視圖,由紅色圓形與心形所組成。當使用者點擊心形或圓形時,圓形的顏色會變成淡灰色,而心形則會變成紅色,同時心形圖示的大小也變得較大。因此,以下是其狀態變化:
如果你使用SwiftUI 來實作可點擊的圓形,以下為程式內容,你可以建立一個 Xcode 的新專案,並將程式插入至 ContentView.swift
來測試看看:
struct ContentView: View {
@State private var circleColorChanged = false
@State private var heartColorChanged = false
@State private var heartSizeChanged = false
var body: some View {
ZStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
Image(systemName: "heart.fill")
.foregroundColor(heartColorChanged ? .red : .white)
.font(.system(size: 100))
.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
}
.onTapGesture {
circleColorChanged.toggle()
heartColorChanged.toggle()
heartSizeChanged.toggle()
}
}
}
我們定義三個狀態變數來建立狀態模型,初始值皆設定為「false」。為了建立圓形與心形,我們使用 ZStack
來將心形疊在圓形上,如圖9.2 所示。SwiftUI 有個 onTapGesture
修飾器可以偵測,點擊手勢,你可以將它加在任何視圖,使其可點擊。在 onTapGesture
閉包中,我們切換狀態,以改變視圖的外觀。
在預覽畫布測試這個 App,則你點擊視圖時,圓形及心形圖示的顏色會改變,但這些變化不會顯示動畫。
要讓這些變化顯示動畫效果,你只需將 animation
修飾器加到 Circle
與 Image
視圖:
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
.animation(.default, value: circleColorChanged)
Image(systemName: "heart.fill")
.foregroundColor(heartColorChanged ? .red : .white)
.font(.system(size: 100))
.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
.animation(.default, value: heartSizeChanged)
SwiftUI 會自動計算與渲染動畫,使視圖可以從一個狀態流暢轉換到另一個狀態。再次點擊心形,你會見到一個平滑動畫。
你不僅可以將 animation
修飾器應用到單一視圖中,還可應用於一組視圖。舉例而言, 你可以將 animation
修飾器加到 ZStack
,將上列的程式碼重新撰寫如下:
ZStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
Image(systemName: "heart.fill")
.foregroundColor(heartColorChanged ? .red : .white)
.font(.system(size: 100))
.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
}
.animation(.default, value: circleColorChanged)
.animation(.default, value: heartSizeChanged)
.onTapGesture {
self.circleColorChanged.toggle()
self.heartColorChanged.toggle()
self.heartSizeChanged.toggle()
}
其工作原理是完全相同的,SwiftUI 尋找嵌入在 ZStack
中所有的狀態變化,並建立動畫。
在範例中,我們使用預設動畫。SwiftUI 提供許多內建動畫供你選擇,包括 linear
、easeIn
、easeOut
、easeInOut
與 spring
。「線性動畫」(linear animation )是以線性速度來呈現變化,而「緩動動畫」(easing animation )則是速度會做變化。詳細內容可以參考 www.easings.net 來了解每個 ease 函數的差異。
要使用其他的動畫,你只需要在 animation
修飾器中設定特定的動畫。例如:你想要使用 spring
動畫,則可以將 .default
更改如下:
.animation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3), value: circleColorChanged)
這會渲染一個基於彈簧特性的動畫,使得心形產生心跳的效果。你可以調整阻尼(damping )值與混合(blend )值,來達到不同的效果。
以上是對視圖使用隱式動畫的方法。我們來看如何使用顯式動畫來達到相同的結果。如前所述,你需要將狀態變化包裹在 withAnimation
區塊內。要建立相同的動畫效果,你可撰寫下列程式碼:
ZStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
Image(systemName: "heart.fill")
.foregroundColor(heartColorChanged ? .red : .white)
.font(.system(size: 100))
.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
}
.onTapGesture {
withAnimation(.default) {
self.circleColorChanged.toggle()
self.heartColorChanged.toggle()
self.heartSizeChanged.toggle()
}
}
我們不再使用 animation
修飾器,而是使用 withAnimation
包裹在 onTapGesture
中。withAnimation
呼叫帶入一個動畫參數,這裡我們指定使用預設動畫。
當然,你可更新 withAnimation
,以將動畫變更為彈簧動畫,如下所示:
withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {
self.circleColorChanged.toggle()
self.heartColorChanged.toggle()
self.heartSizeChanged.toggle()
}
使用顯式動畫,你可以輕鬆控制想加上動畫的狀態。舉例而言,如果你不想為心形圖示的大小變化設定動畫時,則可以從 withAnimation
排除該行程式碼,如下所示:
.onTapGesture {
withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {
self.circleColorChanged.toggle()
self.heartColorChanged.toggle()
}
self.heartSizeChanged.toggle()
}
在這個情況下,SwiftUI 將只對圓形與心形的顏色變化設定動畫,你不會再看到心形圖示的變大動畫效果。
你可能想知道我們是否可以使用隱式動畫來關閉縮放動畫。好的,你仍然可以做到, 可重新調整 .animation
的順序,以防止 SwiftUI 對特定狀態變化設定動畫。下列為達到相同效果的程式:
ZStack {
Circle()
.frame(width: 200, height: 200)
.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
.animation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3), value: circleColorChanged)
Image(systemName: "heart.fill")
.foregroundColor(heartColorChanged ? .red : .white)
.font(.system(size: 100))
.animation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3), value: heartColorChanged)
.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
}
.onTapGesture {
self.circleColorChanged.toggle()
self.heartColorChanged.toggle()
self.heartSizeChanged.toggle()
}
對於 Image 視圖,我們在 scaleEffect 之前置入 animation(nil)
修飾器,這會將動畫取消,scaleEffect
修飾器的狀態變化將不會設定動畫。
雖然你可以使用隱式動畫建立相同的動畫,但我認為在這種情況下,使用顯式動畫會更方便。
SwiftUI 動畫的強大之處在於,你不需關心如何對視圖設定動畫,你只需要提供起始與結束狀態,接著 SwiftUI 會找出其餘的狀態。當你具備了這個觀念,即可以建立各種類型的動畫。
舉例而言,我們來建立一個簡單的下載指示器,你通常可以在一些真實世界的應用程式(如 Medium)中找到它。要建立一個如圖 9.3 所示的下載指示器,我們從開放式圓環(open ended circle )來開始,如下所示:
Circle()
.trim(from: 0, to: 0.7)
.stroke(Color.green, lineWidth: 5)
.frame(width: 100, height: 100)
那麼,我們如何使圓環連續旋轉呢?我們可以利用 rotationEffect
與 animation
修飾器。訣竅就是使圓環 360 度連續旋轉。以下為程式碼:
struct ContentView: View {
@State private var isLoading = false
var body: some View {
Circle()
.trim(from: 0, to: 0.7)
.stroke(Color.green, lineWidth: 5)
.frame(width: 100, height: 100)
.rotationEffect(Angle(degrees: isLoading ? 360 : 0))
.animation(.default.repeatForever(autoreverses: false), value: isLoading)
.onAppear() {
isLoading = true
}
}
}
rotationEffect
修飾器帶入旋轉角度,在上列的程式碼中,我們有一個狀態變數來控制下載狀態。當它設定為true 時,這個旋轉角度設定為 360 度以旋轉圓環。在 animation
修飾器中,我們指定使用預設動畫,不過還有些不同,我們告訴 SwiftUI 要一次又一次重複相同的動畫,這就是建立下載動畫的訣竅。
*備註: 如果在預覽畫布中看不到動畫,請在模擬器中運行App再試一試。*
如果你想要更改動畫的速度,則可以使用線性動畫,並指定持續時間,如下所示:
.animation(.linear(duration: 5).repeatForever(autoreverses: false), value: isLoading)
持續時間越久,則動畫越慢。
onAppear
修飾器對你而言可能比較陌生,如果你對 UIKit 有所了解的話,這個修飾器和 viewDidAppear
非常相似,當視圖出現在畫面上時會自動呼叫。在該程式碼中,我們將下載狀態設定為true,以在視圖載入時啟動動畫。
當你使用此技術,就可以調整設計並開發各種版本的下載指示器。舉例而言,你可以將圓弧疊在圓環上,以建立精美的下載指示器,如圖 9.4 所示。
程式碼片段如下所示:
struct ContentView: View {
@State private var isLoading = false
var body: some View {
ZStack {
Circle()
.stroke(Color(.systemGray5), lineWidth: 14)
.frame(width: 100, height: 100)
Circle()
.trim(from: 0, to: 0.2)
.stroke(Color.green, lineWidth: 7)
.frame(width: 100, height: 100)
.rotationEffect(Angle(degrees: isLoading ? 360 : 0))
.animation(.linear(duration: 1).repeatForever(autoreverses: false), value: isLoading)
.onAppear() {
self.isLoading = true
}
}
}
}
下載指示器不需要為圓形,你也可以使用 Rectangle
或 RoundedRectangle
來建立該指示器。不過,無需更改旋轉角度,你可以修改位移值(offset value)來建立如圖 9.5 所示的動畫。
為了建立動畫,我們將兩個圓角矩形重疊在一起。上面的矩形比下面的矩形短得多, 當開始載入時,我們將位移值從「-110」更新為「110」。
struct ContentView: View {
@State private var isLoading = false
var body: some View {
ZStack {
Text("Loading")
.font(.system(.body, design: .rounded))
.bold()
.offset(x: 0, y: -25)
RoundedRectangle(cornerRadius: 3)
.stroke(Color(.systemGray5), lineWidth: 3)
.frame(width: 250, height: 3)
RoundedRectangle(cornerRadius: 3)
.stroke(Color.green, lineWidth: 3)
.frame(width: 30, height: 3)
.offset(x: isLoading ? 110 : -110, y: 0)
.animation(.linear(duration: 1).repeatForever(autoreverses: false), value: isLoading)
}
.onAppear() {
self.isLoading = true
}
}
}
這會讓綠色矩形沿著線條移動。而且,當你一次又一次重複相同的動畫,它將變成一個載入動畫,圖 9.6 說明了位移值。
下載指示器向使用者提供一些回饋,其表示 App 正在處理某些事情。不過,它並沒有顯示實際進度,如果你需要為使用者提供關於任務進度的更多資訊,則可能需要建立一個如圖 9.7 所示的進度指示器。
建立進度指示器的方式與下載指示器非常相似。不過,你需要使用狀態變數來追蹤進度。以下是建立進度指示器的程式碼片段:
struct ContentView: View {
@State private var progress: CGFloat = 0.0
var body: some View {
ZStack {
Text("\(Int(progress * 100))%")
.font(.system(.title, design: .rounded))
.bold()
Circle()
.stroke(Color(.systemGray5), lineWidth: 10)
.frame(width: 150, height: 150)
Circle()
.trim(from: 0, to: progress)
.stroke(Color.green, lineWidth: 10)
.frame(width: 150, height: 150)
.rotationEffect(Angle(degrees: -90))
}
.onAppear() {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
self.progress += 0.05
print(self.progress)
if self.progress >= 1.0 {
timer.invalidate()
}
}
}
}
}
這裡的狀態變數不是使用布林值,而是我們使用浮點數來儲存狀態。為了顯示進度, 我們以進度值來設定 trim
修飾器。在真實世界的應用程式中,你可以更新 progress
的值來顯示操作的實際進度。為了示範,我們只啟用一個計時器,其每半秒更新一次。
SwiftUI 框架不只讓你可以控制動畫的持續時間,你可以透過 delay
函數來延遲動畫, 如下所示:
Animation.default.delay(1.0)
這會將動畫延遲1 秒後開始。delay
函數也適用其他動畫。
透過混合搭配持續時間值與延遲時間值,你可以實作出一些有趣的動畫,如圖 9.8 所示的圓點下載指示器。
這個指示器由五個點組成,每個點皆有放大縮小動畫,不過各有不同的延遲時間。我們來看程式碼該如何實作:
struct ContentView: View {
@State private var isLoading = false
var body: some View {
HStack {
ForEach(0...4, id: \.self) { index in
Circle()
.frame(width: 10, height: 10)
.foregroundColor(.green)
.scaleEffect(self.isLoading ? 0 : 1)
.animation(.linear(duration: 0.6).repeatForever().delay(0.2 * Double(index)), value: isLoading)
}
}
.onAppear() {
self.isLoading = true
}
}
}
我們先使用一個 HStack
來水平佈局這些圓形,由於這五個圓形(也就是點)皆有相同的大小與顏色,因此我們使用 ForEach
來建立這些圓形。scaleEffect
修飾器是用來縮放圓形的大小,預設是設定為「1」,也就是原來的大小。當開始載入時,該值會更新為「0」, 這將會縮小此點。
用於渲染動畫的程式碼看起來有些複雜,我們來分拆它並逐步研究:
Animation.linear(duration: 0.6).repeatForever().delay(0.2 * Double(index))
第一個部分建立一個持續時間為 0.6 秒的線性動畫,該動畫會重複執行,因此我們呼叫 repeatForever
函數。
如果你沒有呼叫 delay
函數來執行這個動畫,則所有的點會同時縮放。但是,這不是我們想要的結果,每個點應獨立調整大小,而不是全部同時放大/ 縮小,這也是為何我們呼叫 delay
函數的原因,並對每個點使用不同的延遲時間值。
你可以更改持續時間值與延遲時間值來調整動畫。
有時,你可能需要將一個形狀(例如:矩形)流暢地變形為另一個形狀(例如:圓形)。這該如何實作呢?使用內建的形狀與動畫,你可以輕鬆建立如圖 9.9 所示的變形。
將矩形變形為圓形的技巧是使用 RoundedRectangle
形狀,並為圓角半徑的變化設定動畫。假設矩形的寬度與高度相同,當圓角半徑設定為寬度的一半時,它會變為圓形。以下是變形按鈕的實作:
struct ContentView: View {
@State private var recordBegin = false
@State private var recording = false
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: recordBegin ? 30 : 5)
.frame(width: recordBegin ? 60 : 250, height: 60)
.foregroundColor(recordBegin ? .red : .green)
.overlay(
Image(systemName: "mic.fill")
.font(.system(.title))
.foregroundColor(.white)
.scaleEffect(recording ? 0.7 : 1)
)
RoundedRectangle(cornerRadius: recordBegin ? 35 : 10)
.trim(from: 0, to: recordBegin ? 0.0001 : 1)
.stroke(lineWidth: 5)
.frame(width: recordBegin ? 70 : 260, height: 70)
.foregroundColor(.green)
}
.onTapGesture {
withAnimation(Animation.spring()) {
self.recordBegin.toggle()
}
withAnimation(Animation.spring().repeatForever().delay(0.5)) {
self.recording.toggle()
}
}
}
}
這裡有兩個狀態變數:recordBegin
與 recording
,其是控制兩個單獨的動畫。第一個變數控制按鈕的變形,如前所述,我們使用圓角半徑來實現這個變形。矩形的寬度原先是設定為「250 點」,當使用者點擊矩形來觸發變形時,這個框架的寬度會變為「60 點」。隨著改變,圓角半徑變成「30 點」,也就是寬度的一半。
這就是我們將矩形變形為圓形的方法,而且SwiftUI 會自動渲染此變形的動畫。
另一方面,recording
狀態變數處理了麥克風圖片的縮放。當它為錄音狀態時,我們將縮放比例從「1」更改為「0.7」,藉由重複執行相同的動畫,即可建立放大及縮小的動畫。
請注意,以上的程式碼使用顯式方法來對視圖設定動畫。這不是強制性的,依照自己的喜好,你也可以使用隱式動畫方法來獲得相同的結果。
到目前為止,我們已經討論了對視圖層次(view hierarchy )中已存在的視圖設定動畫。我們建立動畫來放大和縮小視圖,或者對視圖大小設定動畫。
SwiftUI 讓開發者不只是做出前述的動畫,你可以定義如何從視圖層次中插入或移除視圖,而在 SwiftUI 中,這就是所謂的「轉場」( transition )。框架預設是使用淡入( fade in )與淡出( fade out )轉場。不過它內建了幾個現成的轉場效果,如滑動( slide )、移動( move)、不透明度(opacity )等。當然,你可以開發自己的轉場效果,也可以簡單的混合搭配各種類型的轉場,以建立所需的轉場效果。
我們來看一個簡單的範例,以更加了解轉場的含義以及動畫如何運作。建立一個名為 SwiftUITransition
的新專案,並更新 ContentView
如下:
struct ContentView: View {
var body: some View {
VStack {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.foregroundColor(.green)
.overlay(
Text("Show details")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(.white)
)
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.foregroundColor(.purple)
.overlay(
Text("Well, here is the details")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(.white)
)
}
}
}
在上列的程式碼中,我們使用 VStack
垂直佈局兩個矩形。而我要做的是讓這個堆疊可以點擊。首先,要隱藏紫色矩形,只有當使用者點擊綠色矩形(也就是Show details )時才會顯示。
為此,我們需要宣告一個狀態變數來決定是否顯示紫色矩形。將下列這行程式碼插入 ContentView
中:
@State private var show = false
接下來,我們將紫色矩形包裹在 if
敘述中,如下所示:
if show {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.foregroundColor(.purple)
.overlay(
Text("Well, here is the details")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(.white)
)
}
對於 VStack
,我們加入 onTapGesture
函數來偵測點擊,並為狀態變化建立動畫。請注意,該轉場效果與動畫要有關聯,否則它無法自行運作。
.onTapGesture {
withAnimation(Animation.spring()) {
self.show.toggle()
}
}
當使用者點擊堆疊時,我們切換為 show
變數來顯示紫色矩形。如果你在模擬器或預覽畫布中執行這個 App,則應該只會看到綠色矩形,如圖 9.12 所示。點擊它會顯示紫色矩形,而你應可看到一個平滑的淡入與淡出的轉場效果。
如前所述,如果你沒有指定想使用的轉場效果,SwiftUI 將渲染淡入淡出轉場。要使用其他的轉場效果,則在紫色矩形中加入 transition
修飾器,如下所示:
if show {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 300)
.foregroundColor(.purple)
.overlay(
Text("Well, here is the details")
.font(.system(.largeTitle, design: .rounded))
.bold()
.foregroundColor(.white)
)
.transition(.scale(scale: 0, anchor: .bottom))
}
這個 transition
修飾器會帶入一個 AnyTransition
型別的參數。這裡我們使用scale 轉場, 錨點(anchor )設定為 .bottom
,這就是你修改轉場效果所需要做的事情。在模擬器中執行該 App,並看一下結果為何,當 App 顯示紫色矩形時,你應該會看到一個彈出動畫。我建議使用內建的模擬器來測試動畫,而不採用 App 預覽的方式,因為預覽畫布可能無法正確的渲染轉場動作。
除了 .scale 之外,SwiftUI 框架還有多個內建的轉場效果,包括 .opaque
、.offset
、.move
與 .slide
。試著以位移轉場(offset transition)取代縮放轉場(scale transition),如下所示:
.transition(.offset(x: -600, y: 0))
如此,當紫色矩形插入 VStack
時,它會從左側滑入。
你可以呼叫 combined(with:)
方法來將兩個或者更多個轉場效果結合在一起,以建立更流暢的轉場效果。舉例而言,如果要結合位移與縮放動畫,則可以撰寫程式碼如下:
.transition(AnyTransition.offset(x: -600, y: 0).combined(with: .scale))
如果需要混合三個轉場效果的話,則可以參考以下這行的範例程式碼:
.transition(AnyTransition.offset(x: -600, y: 0).combined(with: .scale).combined(with: .opacity))
在某些情況下,如果你需要定義一個可以重複利用的動畫,你可以在 AnyTransition
定義一個擴展(extension ),如下所示:
extension AnyTransition {
static var offsetScaleOpacity: AnyTransition {
AnyTransition.offset(x: -600, y: 0).combined(with: .scale).combined(with: .opacity)
}
}
接著,你可以在 transition
修飾器中使用 offsetScaleOpacity
動畫:
.transition(.offsetScaleOpacity)
執行這個App,並再次測試轉場效果,看起來很棒吧?
我們剛才討論的轉場皆是對稱性的,也就是視圖的插入與移除是使用相同的轉場效果。舉例而言,如果將縮放轉場運用於視圖,則 SwiftUI 在它插入視圖層次時會放大視圖,而移除它時,該框架會將其縮回原來大小。
那麼,若你想在插入視圖時使用縮放轉場以及移除視圖時使用位移轉場呢?這在 SwiftUI 中,即所謂的「不對稱轉場」( Assymetric Transitions )。要使用這種轉場效果非常簡單,你只需要呼叫 .assymetric
方法,來指定插入( insertion )及移除(removal )的轉場即可。下列是範例程式碼:
.transition(.asymmetric(insertion: .scale(scale: 0, anchor: .bottom), removal: .offset(x: -600, y: 0)))
另外,如果你需要重新使用這個轉場,則可以在 AnyTransition
定義一個擴展,如下所示:
extension AnyTransition {
static var scaleAndOffset: AnyTransition {
AnyTransition.asymmetric(
insertion: .scale(scale: 0, anchor: .bottom),
removal: .offset(x: -600, y: 00)
)
}
}
更改程式碼後,請使用內建模擬器來執行這個 App。當紫色矩形出現於螢幕上時,你應該會看到縮放轉場,而當你點擊矩形時,紫色矩形會滑出螢幕畫面。
現在,你應該具備了轉場與動畫的概念,我們來挑戰建立一個精美的按鈕,以顯示目前操作狀態。請輸入下列網址:https://www.appcoda.com/wp-content/uploads/2019/10/swiftui-animation-16.gif,來看一下效果。
這個按鈕有三種狀態:
這是一個非常具挑戰性的專案,它將測試你對 SwiftUI 動畫與轉場的了解。你將需要結合到目前為止所學的知識來制定解決方案。
如圖 9.16 所示的範例按鈕,這個處理狀態大約是 4 秒處理狀態約需要 4 秒,你不需要執行實際操作。作為提示,我使用下列的程式碼來模擬該操作。
private func startProcessing() {
self.loading = true
// 使用DispatchQueue.main.asyncAfter 來模擬操作
// 在真實世界的專案中,你將在這裡執行一個任務
// 當任務完成之後,你將完成狀態設定為true
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
self.completed = true
}
}
你已經學會了如何實作視圖轉場,試著結合在第 5 章中建立的卡片視圖專案,並建立如下所示的視圖轉場。當使用者點擊卡片時,目前的視圖將縮小並淡出,下一個視圖將以放大動畫顯示在前面。
如果你不懂上述的動畫, 則可以輸入網址: https://www.appcoda.com/wp-content/uploads/2019/10/swiftui-view-animation.gif,來看一下動畫效果。
動畫在行動 UI 設計中扮演著一個特殊的角色,精心設計的動畫可改善使用者體驗,並讓 UI 的互動具有意義。兩個視圖之間流暢輕快的轉場,將讓使用者滿意且印象深刻。在 App Store 上有超過 200 萬支 App,要使你的 App 脫穎而出,並不容易,不過精心設計的 UI 與動畫肯定會產生根本的差別。
話雖如此,但是對於有經驗的開發者而言,撰寫平滑動畫動畫也不是一件容易的事。幸運的是,SwiftUI 框架簡化了 UI 動畫與轉場的開發,你只須告訴框架:視圖在開始及結束時的狀態為何,SwiftUI 會計算出其餘的狀態,為你渲染出一個流暢且漂亮的動畫。
在本章中,我已介紹了基本的原理,不過如你所見,你已經建立了一些令人愉悅的動畫與轉場效果,最重要的是,只需要幾行程式碼即能辦到。
我期望你喜歡本章的內容,並發掘出有用的技巧。在本章所準備的範例檔中,有完整的專案與作業解答可以下載: