最近有讀者提問:如何利用 SwiftUI 設置漸變動畫 (animated gradient)、或創建漸變背景動畫?SwiftUI 框架有許多內置組件,例如 LinearGradient
和 AngularGradient
,讓開發者設置漸變效果。另外,SwiftUI 也有 .animation
修飾符 (modifier),讓開發者可以簡單地創建動畫。問題是,我們如何結合漸變組件和 .animation
修飾符來創建漸變動畫呢?
在這篇教學文章中,我們會用以下 3 種方法來實作漸變動畫:
- 更改
startPoint
和endPoint
- 利用
.hueRotation
修飾符 - 利用
AnimatableModifier
在編寫這篇文章時,我是使用 Xcode 13 版本的,但文章的程式碼也應該適用於 Xcode 12。
更改 startPoint 和 endPoint 來實作漸變動畫
首先,讓我們從一個簡單的線性漸變 (linear gradient) 開始。在 Xcode 創建一個 SwiftUI 專案後,我們就可以在 ContentView
中加入以下程式碼:
LinearGradient(colors: [.purple, .yellow], startPoint: .topLeading, endPoint: .bottomTrailing)
.ignoresSafeArea()
以上的程式碼會創建出一個漸變背景,從左上角的紫色向右下角漸變到黃色。
接下來,我們就要更改 startPoint
和 endPoint
,來為漸變顏色設置動畫。動畫開始時,startPoint
會從左上角變為左下角,而 endPoint
就會從右下角變為右上角。為了實作動畫,讓我們宣告一個狀態變數 (state variable) 來保存動畫的狀態:
@State private var animateGradient = false
讓我們如此更新 LinearGradient
視圖的程式碼:
LinearGradient(colors: [.purple, .yellow], startPoint: animateGradient ? .topLeading : .bottomLeading, endPoint: animateGradient ? .bottomTrailing : .topTrailing)
.ignoresSafeArea()
.onAppear {
withAnimation(.linear(duration: 2.0).repeatForever(autoreverses: true)) {
animateGradient.toggle()
}
}
我們在視圖出現時,開始利用動畫來呈現 startPoint
和 endPoint
的變化。為了示範,我們在這裡只用了線性動畫,並設置為連續執行。
然後,讓我們在預覽面板中運行範例,來看看漸變動畫。
同樣的方法可以應用於不同的漸變效果,例如 RadialGradient
。我們可以如此修改程式碼來試試看:
RadialGradient(colors: [.purple, .yellow], center: .center, startRadius: animateGradient ? 400 : 200, endRadius: animateGradient ? 20 : 40)
.ignoresSafeArea()
.onAppear {
withAnimation(.linear(duration: 2.0).repeatForever(autoreverses: true)) {
animateGradient.toggle()
}
}
利用色調旋轉 (Hue Rotation) 來實作漸變動畫
與第一個方法不同,這個方法是透過改變色調旋轉 (hue rotation) 角度,來創建動畫漸變。SwiftUI 有一個內置修飾符名為 .hueRotation
,讓我們按指定角度轉換視圖中所有色調。
舉個例子,如果我們在線性漸變中附加了 .hueRotation
修飾符,並如此以 45 度旋轉色調:
LinearGradient(colors: [.purple, .yellow], startPoint: .topLeading, endPoint: .bottomTrailing)
.hueRotation(.degrees(45))
.ignoresSafeArea()
漸變顏色就會調整成這樣:
然後,我們就可以色調旋轉角度的變化進行動畫處理,來創建漸變動畫。讓我們在宣告 animateGradient
狀態變數後,如此修改 .hueRotation
修飾符:
LinearGradient(colors: [.purple, .yellow], startPoint: .topLeading, endPoint: .bottomTrailing)
.hueRotation(.degrees(animateGradient ? 45 : 0))
.ignoresSafeArea()
.onAppear {
withAnimation(.easeInOut(duration: 5.0).repeatForever(autoreverses: true)) {
animateGradient.toggle()
}
}
我們將狀態變數切換為 true
後,SwiftUI 就會動畫化色調旋轉角度的變化,來呈現一個漸變動畫。
利用 AnimatableModifier 來實作漸變動畫
我們的視圖本來是從上而下由紫色漸變成黃色,如果我們想把兩種漸變顏色轉換成其他顏色,應該如何實作這樣的漸變動畫呢?
如果是這樣,這個方法就最適合了,只是步驟也會比前面兩個方法複雜。
在 Swift 中,我們可以利用 Gradient
結構定義一個漸變,看看以下例子:
let gradient1 = Gradient(colors: [.purple, .yellow])
let gradient2 = Gradient(colors: [.blue, .purple])
如果是從一組顏色轉換成另一組顏色的改變,SwiftUI 無法自動為此設置動畫,因此我們需要利用 AnimatableModifier
協定,來構建自己的實作方法。如果你不了解這個協定,可以參考我們《精通 SwiftUI》一書。
首先,讓我們建立一個 AnimatableGradientModifier
結構,並調用 AnimatableModifier
協定,來為漸變顏色的改變創建動畫。
struct AnimatableGradientModifier: AnimatableModifier {
let fromGradient: Gradient
let toGradient: Gradient
var progress: CGFloat = 0.0
var animatableData: CGFloat {
get { progress }
set { progress = newValue }
}
func body(content: Content) -> some View {
var gradientColors = [Color]()
for i in 0..<fromGradient.stops.count {
let fromColor = UIColor(fromGradient.stops[i].color)
let toColor = UIColor(toGradient.stops[i].color)
gradientColors.append(colorMixer(fromColor: fromColor, toColor: toColor, progress: progress))
}
return LinearGradient(gradient: Gradient(colors: gradientColors), startPoint: .topLeading, endPoint: .bottomTrailing)
}
func colorMixer(fromColor: UIColor, toColor: UIColor, progress: CGFloat) -> Color {
guard let fromColor = fromColor.cgColor.components else { return Color(fromColor) }
guard let toColor = toColor.cgColor.components else { return Color(toColor) }
let red = fromColor[0] + (toColor[0] - fromColor[0]) * progress
let green = fromColor[1] + (toColor[1] - fromColor[1]) * progress
let blue = fromColor[2] + (toColor[2] - fromColor[2]) * progress
return Color(red: Double(red), green: Double(green), blue: Double(blue))
}
}
這個結構會以最初的漸變 (fromGradient
) 和目標漸變 (toGradient
) 為輸入參數 (parameter)。progress
變數會追踪漸變的變化,初始值會設置為 0
。當該值設置為 1
時,就代表漸變顏色完全變為 toGradient
的顏色組合。
我在前文提過,我們需要構建自己的方法來實作這種漸變動畫呢。在上面的程式碼中,我們創建了一個 colorMixer
函數(特別鳴謝:SwiftUI-lab),來根據 fromColor
、toColor
、和特定的 progress
計算結果顏色。
progress
數值隨著時間變化,body
就會使用計算出的顏色創建 LinearGradient
視圖。
如此一來,我們就可以動畫化這種從一組顏色轉換成另一組顏色的改變。為方便起見,讓我們創建一個視圖擴充功能 (extension) 來應用 AnimatableGradientModifier
:
extension View {
func animatableGradient(fromGradient: Gradient, toGradient: Gradient, progress: CGFloat) -> some View {
self.modifier(AnimatableGradientModifier(fromGradient: fromGradient, toGradient: toGradient, progress: progress))
}
}
現在,我們可以如此更新 ContentView
來使用 animatableGradient
修飾符:
struct ContentView: View {
@State private var progress: CGFloat = 0
let gradient1 = Gradient(colors: [.purple, .yellow])
let gradient2 = Gradient(colors: [.blue, .purple])
var body: some View {
Rectangle()
.animatableGradient(fromGradient: gradient1, toGradient: gradient2, progress: progress)
.ignoresSafeArea()
.onAppear {
withAnimation(.linear(duration: 5.0).repeatForever(autoreverses: true)) {
self.progress = 1.0
}
}
}
}
我試過在預覽面板執行 App,但它未能呈現動畫。讓我們使用 iPhone 模擬器來測試漸變動畫。