精通 SwiftUI - iOS 17 版

第 55 章
使用 KeyframeAnimator 建立進階動畫

除了 PhaseAnimator,SwiftUI 在 iOS 17 中還引入了 KeyframeAnimator,允許開發者使用關鍵幀(keyframe)建立更進階動畫。在本教程中,我們將深入研究 KeyframeAnimator,並學習如何創建更複雜的動畫。

我們在前一個教學中討論的 PhaseAnimator 視圖(或修飾器)為開發者提供了在一系列相位中創建多步驟動畫的能力。通過為每個相位指定所需的動畫,PhaseAnimator 在相位變化時自動對內容進行動畫處理。它通過為你處理相位之間的過渡,簡化了構建複雜動畫的過程。

使用 KeyframeAnimator

對於階段性的動畫(Phase-based Animation),它適用於可以表示為離散狀態的動畫。當狀態轉換發生時,所有屬性都同時進行動畫。一旦特定狀態的動畫完成,SwiftUI 將平滑地過渡到下一個狀態。這個過程會應用到所有動畫階段中。

基於關鍵幀的動畫(Keyframe-based animation)旨在應對一種特定類型的動畫,其中每個屬性都可以獨立進行動畫化。通過利用關鍵幀,我們可以分別對個別的屬性建立動畫,從而為我們的動畫提供更大的靈活性和控制能力。

圖 55.1. 使用 KeyframeAnimator 建立的範例動畫
圖 55.1. 使用 KeyframeAnimator 建立的範例動畫

讓我們嘗試對一個表情符號圖標進行動畫(如上所示),你將理解我們如何使用關鍵幀動畫器(keyframe animator)。

定義動畫值

如前所述,基於關鍵幀的動畫使我們能夠獨立地對個別的屬性進行動畫。為了利用關鍵幀動畫器,我們首先定義一個結構,該結構包含我們希望進行動畫的所有屬性。以下是一個示例:

struct AnimationValues {
    var scale = 1.0
    var verticalStretch = 1.0
    var translation = CGSize.zero
    var opacity = 1.0
}

初始值定義了表情符號圖標的初始狀態。稍後,我們將更改每個屬性以縮放、拉伸和移動表情符號圖標。

應用關鍵幀動畫器

body 閉包中,讓我們更新代碼,以應用關鍵幀動畫器:

Text("🐻")
    .font(.system(size: 100))
    .keyframeAnimator(initialValue: AnimationValues()) { 
                content, value in

        content
            .scaleEffect(value.scale)
            .scaleEffect(y: value.verticalStretch)
            .offset(value.translation)
            .opacity(value.opacity)

    } keyframes: { _ in

        KeyframeTrack(\.scale) {
            CubicKeyframe(0.8, duration: 0.2)
        }
    }

像往常一樣,我們使用 Text 視圖來顯示表情符號圖標。要創建基於關鍵幀的動畫,我們將 keyframeAnimator 修飾器附加到文本視圖上。

initialValue 參數提供了關鍵幀將從中進行動畫的初始值。在視圖構建器閉包內,我們可以訪問兩個參數。第一個參數是代表修改後的視圖的代理值。第二個參數保存了關鍵幀生成的插值值。

我們通過調整視圖的縮放、偏移和不透明度來應用所需的動畫效果。最後是 keyframes 參數。在這裡,我們定義隨時間變化的值。這些定義的關鍵幀將負責對指定的值應用相應的動畫。

關鍵幀按照軌道排列,每個軌道控制動畫類型的不同屬性。在提供的程式碼片段中,我們使用 CubicKeyframe 類型特別指定了縮放屬性的關鍵幀軌道。我們調整表情符號的大小,將其縮小到原始大小的80%。

圖 55.2. 縮放動畫
圖 55.2. 縮放動畫

一旦你修改程式碼之後,你應該能夠立即在預覽畫布中看到動畫效果。關鍵幀動畫器將對大小變化進行動畫處理並且不斷重複。

關鍵幀類型

雖然我們使用了 CubicKeyframe 類型,但實際上有四種不同的關鍵幀類型可供使用:

  • LinearKeyframe - 在向量空間中從前一個關鍵幀線性插值。
  • SpringKeyframe - 使用彈簧函數從前一個關鍵幀插值到目標值。
  • CubicKeyframe - 使用三次貝塞爾曲線(cubic Bézier curve)在關鍵幀之間進行插值。
  • MoveKeyframe - 立即跳至一個值,不進行插值。

嘗試探索和測試不同的關鍵幀類型和持續時間,觀察它們的行為。通過嘗試不同的關鍵幀類型和調整持續時間,你可以更深入地了解它們對動畫的影響和塑造方式。

目前,我們只對 scale 屬性應用了單一變化。你可以自由定義隨時間變化的其他值。以下是一個示例:

KeyframeTrack(\.scale) {
    CubicKeyframe(0.8, duration: 0.2)
    CubicKeyframe(0.6, duration: 0.3)
    CubicKeyframe(1.0, duration: 0.3)
    CubicKeyframe(0.8, duration: 0.2)
    CubicKeyframe(0.6, duration: 0.3)
    CubicKeyframe(1.0, duration: 0.3)
}

The code describes the scale factor at specific times within the animation. In the preview canvas, you'll notice a smoother and more fluid animation for the emoji icon.

圖 55.3. 更流暢的動畫
圖 55.3. 更流暢的動畫

多個關鍵幀軌道(Multiple Keyframe Tracks)

到目前為止,我們專注於單個關鍵幀軌道來改變比例因子。然而,通過定義獨立的軌道,每個軌道具有自己獨特的定時,關鍵幀提供了獨立地動畫多個效果的能力。通過結合多個軌道,我們可以同時動畫各種屬性,從而創建更高級的動畫。

在同一個示例中,我們可以為垂直拉伸、平移和不透明度屬性定義獨立的關鍵幀軌道。以下是示例程式碼:

Text("🐻")
    .font(.system(size: 100))
    .keyframeAnimator(initialValue: AnimationValues()) { content, value in

        content
            .scaleEffect(value.scale)
            .scaleEffect(y: value.verticalStretch)
            .offset(value.translation)
            .opacity(value.opacity)

    } keyframes: { _ in
        KeyframeTrack(\.verticalStretch) {
            LinearKeyframe(1.2, duration: 0.1)
            SpringKeyframe(2.0, duration: 0.2, spring: .snappy)
            CubicKeyframe(1.05, duration: 0.3)
            CubicKeyframe(1.2, duration: 0.2)
            CubicKeyframe(1.1, duration: 0.32)
            CubicKeyframe(1.2, duration: 0.2)
            CubicKeyframe(1.05, duration: 0.25)
            CubicKeyframe(1.3, duration: 0.23)
            CubicKeyframe(1.0, duration: 0.3)
        }

        KeyframeTrack(\.scale) {
            CubicKeyframe(0.8, duration: 0.2)
            CubicKeyframe(0.6, duration: 0.3)
            CubicKeyframe(1.0, duration: 0.3)
            CubicKeyframe(0.8, duration: 0.2)
            CubicKeyframe(0.6, duration: 0.3)
            CubicKeyframe(1.0, duration: 0.3)
        }

        KeyframeTrack(\.translation) {
            SpringKeyframe(CGSize(width: 100, height: 100), duration: 0.4)
            SpringKeyframe(CGSize(width: -50, height: -300), duration: 0.4)
            SpringKeyframe(.zero, duration: 0.2)
            SpringKeyframe(CGSize(width: -50, height: 200), duration: 0.3)
            SpringKeyframe(CGSize(width: -90, height: 300), duration: 0.3)
            SpringKeyframe(.zero, duration: 0.4)
        }

        KeyframeTrack(\.opacity) {
            LinearKeyframe(0.5, duration: 0.2)
            LinearKeyframe(1.0, duration: 0.23)
            LinearKeyframe(0.7, duration: 0.25)
            LinearKeyframe(1.0, duration: 0.33)
            LinearKeyframe(0.8, duration: 0.2)
            LinearKeyframe(1.0, duration: 0.23)
        }
    }

通過使用多個關鍵幀軌道,我們可以實現一個有趣的動畫效果。在這種情況下,表情符號圖標將在隨機位置移動,同時其不透明度在特定時間點變化。

圖 55.4. 左右移動動畫
圖 55.4. 左右移動動畫

在預設的情況下,關鍵幀動畫器會持續執行動畫。如果你想停止動畫,可以將 repeat 參數設置為 false

你可以使用 ZStack 和重疊另一個表情符號圖標來創建如下所示的動畫效果。我將將這作為一個練習,讓你自行探索和實現。

圖 55.5. 帶有兩個表情符號圖示的複雜動畫
圖 55.5. 帶有兩個表情符號圖示的複雜動畫

總結

KeyframeAnimator 是引入到 iOS 17 中的一個寶貴功能。與 PhaseAnimator 修飾符相比,這個在 SwiftUI 中的新工具使開發者能夠使用關鍵幀創建高級動畫。

通過利用關鍵幀,開發者可以在時間軸上定義特定的時間點並精確地操作動畫的屬性。這種增強的控制力使得可以創建複雜而動態的視覺效果,從而實現更流暢的動畫。

你可以在這裡下載示例項目以供參考: