IV-1a 文字跑馬燈

所謂「跑馬燈(Marquee)」就是一段文字從右到左跑過顯示器的效果,經常會在銀行、醫院、車站等公眾場所看到,對於有限的顯示空間(例如LED顯示器)特別好用。

本課就模擬火車站月台上看到的「南下列車將於1分鐘後進站」當做範例,用SwiftUI寫一個跑馬燈的程式。程式碼如下:

// 4-1a 跑馬燈 Text Animation
// Created by Heman, 2022/02/27
import PlaygroundSupport
import SwiftUI

var 訊息 = "南下列車即將於1分鐘後進站"

struct 跑馬燈: View {
    @State var 位移: CGFloat = 450
    let 動畫效果 = Animation.linear(duration: 10.0)
    var body: some View {
        Text(訊息)
            .foregroundColor(.purple)
            .font(.system(size: 36))
            .frame(width: 800.0)
            .lineLimit(1)
            .offset(x: 位移, y: 0)
            .onAppear {
                withAnimation(動畫效果.repeatForever(autoreverses: false)) {
                    位移 = -450
                }
            }
    }
}

PlaygroundPage.current.setLiveView(跑馬燈())

這段文字「南下列車即將於1分鐘後進站」之所以能動起來,主要就是利用SwiftUI的「Animation物件」,使用方法分為兩個步驟。

第一,先定義一個Animation物件實例:

let 動畫效果 = Animation.linear(duration: 10.0)

Animation 是物件類型(Type),有多種類型方法(Type method)可用來產出物件實例,這幾種方法分別對應不同的時間曲線,我們採用的 linear(duration: 10.0) 是線性變化,設定變化時間是10秒。

SwiftUI Animation 物件支援的時間曲線可參考下圖,包括線性 linear、緩入 easeIn、緩出 easeOut、緩入緩出 easeInOut、彈簧 spring、可自行定義時間曲線 timingCurve...等等。只有linear是定速,變化最平均,其他非線性的時間曲線,速度都會有所變化,產生更豐富的動畫效果。

圖片來源:https://www.objc.io/blog/2019/09/26/swiftui-animation-timing-curves/

圖片來源:https://www.objc.io/blog/2019/09/26/swiftui-animation-timing-curves/

第二步驟是在視圖一出現時(.onAppear),呼叫 withAnimation() 啟用動畫效果,withAnimation() 是一個「全域函式」(global function),意思是函式的有效範圍最大,在程式任何地方均可呼叫,同時也表示 withAnimation() 不屬於任何物件,這在SwiftUI物件庫中是比較少見的。

withAnimation() 需要一個Animation物件實例當作參數,之後再套一層「匿名函式」:

.onAppear {
	withAnimation(動畫效果.repeatForever(autoreverses: false)) {
		位移 = -450
	}
}

這裡參數就用剛定義出來的Animation物件實例「動畫效果」,每個Animation物件實例都帶有若干實例方法(Instance method),可用來調整動畫行為,用法與視圖修飾語(View modifier)類似。Animation的實例方法包括:

其中repeatCount()與repeatForever()還可再加一個參數 autoreverses,表示要不要「倒轉」,預設是要倒轉(autoreverses: true)。如果4-1a範例程式用了預設倒轉,文字就會變成左右來回跑,而不是單向從右到左的跑馬燈,所以要加上(autoreverses: false)參數。

在後面跟隨的匿名函式中,我們將狀態變數(@State var)「位移」從原本的 450(單位是畫素「點」)改為-450,這個改變會觸發視圖的狀態變化,從而啟動動畫效果,關鍵的幾行程式碼如下:

@State var 位移: CGFloat = 450
let 動畫效果 = Animation.linear(duration: 10.0)
Text(訊息)
	.offset(x: 位移, y: 0)
	.onAppear {
		withAnimation(動畫效果.repeatForever(autoreverses: false)) {
			位移 = -450
		}
	}