動畫實作

在 SwiftUI 中設置漸變動畫 (animated gradient) 的 3 個方法

最近有讀者提問:如何利用 SwiftUI 設置漸變動畫 (animated gradient)、或創建漸變背景動畫?SwiftUI 框架有許多內置組件,例如 LinearGradient 和 AngularGradient,讓開發者設置漸變效果。另外,SwiftUI 也有 .animation 修飾符 (modifier),讓開發者可以簡單地創建動畫。問題是,我們如何結合漸變組件和 .animation 修飾符來創建漸變動畫呢?
在 SwiftUI 中設置漸變動畫 (animated gradient) 的 3 個方法
在 SwiftUI 中設置漸變動畫 (animated gradient) 的 3 個方法
In: 動畫實作, SwiftUI 框架

最近有讀者提問:如何利用 SwiftUI 設置漸變動畫 (animated gradient)、或創建漸變背景動畫?SwiftUI 框架有許多內置組件,例如 LinearGradientAngularGradient,讓開發者設置漸變效果。另外,SwiftUI 也有 .animation 修飾符 (modifier),讓開發者可以簡單地創建動畫。問題是,我們如何結合漸變組件和 .animation 修飾符來創建漸變動畫呢?

在這篇教學文章中,我們會用以下 3 種方法來實作漸變動畫:

  1. 更改 startPointendPoint
  2. 利用 .hueRotation 修飾符
  3. 利用 AnimatableModifier

在編寫這篇文章時,我是使用 Xcode 13 版本的,但文章的程式碼也應該適用於 Xcode 12。

更改 startPoint 和 endPoint 來實作漸變動畫

首先,讓我們從一個簡單的線性漸變 (linear gradient) 開始。在 Xcode 創建一個 SwiftUI 專案後,我們就可以在 ContentView 中加入以下程式碼:

LinearGradient(colors: [.purple, .yellow], startPoint: .topLeading, endPoint: .bottomTrailing)
    .ignoresSafeArea()

以上的程式碼會創建出一個漸變背景,從左上角的紫色向右下角漸變到黃色。

swiftui-animated-gradient

接下來,我們就要更改 startPointendPoint,來為漸變顏色設置動畫。動畫開始時,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()
        }
    }

我們在視圖出現時,開始利用動畫來呈現 startPointendPoint 的變化。為了示範,我們在這裡只用了線性動畫,並設置為連續執行。

然後,讓我們在預覽面板中運行範例,來看看漸變動畫。

swiftui-animating-gradient-change

同樣的方法可以應用於不同的漸變效果,例如 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()

漸變顏色就會調整成這樣:

swiftui-animating-gradient-hue-rotation

然後,我們就可以色調旋轉角度的變化進行動畫處理,來創建漸變動畫。讓我們在宣告 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 就會動畫化色調旋轉角度的變化,來呈現一個漸變動畫。

swiftui-gradient-animation

利用 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),來根據 fromColortoColor、和特定的 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 模擬器來測試漸變動畫。

swiftui-gradient-animation-animatablemodifier
譯者簡介:Kelly Chan-AppCoda 編輯小姐。

作者
Simon Ng
軟體工程師,AppCoda 創辦人。著有《iOS 16 App 程式設計實戰心法》、《iOS 16 App程式設計進階攻略》以及《精通SwiftUI》。曾任職於HSBC, FedEx等跨國企業,專責軟體開發、系統設計。2012年創立AppCoda技術部落格,定期發表iOS程式教學文章。現時專注發展AppCoda業務,致力於iOS程式教學、產品設計及開發。你可以到推特與我聯絡。
評論
更多來自 AppCoda 中文版
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。