精通 SwiftUI - iOS 17 版

第 54 章
使用 PhaseAnimator 創建動態的動畫

SwiftUI已經簡化了視圖動畫的創建。其中一個例子是 matchedGeometryEffect 修飾器,它使開發者能夠定義兩個視圖的外觀。該修飾符計算兩個視圖之間的差異並自動動畫化大小和位置的變化。在iOS 17中,蘋果在SwiftUI框架中繼續改進了一個名為 PhaseAnimator 的新視圖,使我們能夠創建更複雜的動畫。

在本教學中,我們將探索 PhaseAnimator 的能力,並學習如何利用它來創建多步驟(Multi-step)的動畫。

使用 PhaseAnimator 創建簡單的動畫

PhaseAnimator視圖或.phaseAnimator` 修飾器使你能夠生成多步驟的動畫。通過循環遍歷你提供的表示不同步驟的相位集合,你可以創建動態且引人入勝的動畫。

圖 54.1. 使用 PhaseAnimator 建立的範例動畫
圖 54.1. 使用 PhaseAnimator 建立的範例動畫

讓我舉個簡單的例子,這樣你就能理解如何使用相位動畫器。我們將對一個圓角矩形進行變換動畫。它開始時是一個藍色的矩形,然後進行縮放,顏色變為靛藍色,並添加了一個3D旋轉動畫。

我們可以使用 RoundedRectangle 視圖來創建圓角矩形,並將 phaseAnimator 修飾符附加到矩形上,如下所示:

struct ContentView: View {
    var body: some View {
        RoundedRectangle(cornerRadius: 25.0)
            .frame(height: 200)
            .phaseAnimator([ false, true ]) { content, phase in
                content
                    .scaleEffect(phase ? 1.0 : 0.5)
                    .foregroundStyle(phase ? .indigo : .blue)
            }
    }
}

phaseAnimator 中,我們指定了兩個階段:falsetrue。視圖構建器閉包接受兩個參數。第一個參數是代表修改後視圖的代理值。第二個參數表示當前的階段。

當視圖最初出現時,第一個相位(即 false)是活動的。我們將縮放設置為原始尺寸的50%,前景色設置為藍色。在第二個相位中,矩形恢復到原始大小,顏色過渡為靛藍色。

階段動畫器會自動動畫化這兩個階段之間的變化。

圖 54.2. 動畫顯示兩個階段之間的變化
圖 54.2. 動畫顯示兩個階段之間的變化

要創建3D旋轉動畫,你可以將 rotation3DEffect 修飾器附加到 content 視圖上,如下所示:

.rotation3DEffect(
    phase ? .degrees(720) : .zero,
                          axis: (x: 0.0, y: 1.0, z: 0.0)
)

如果你想自定義動畫,phaseAnimator 還提供了 animation 參數,用於定義你喜歡的動畫效果。根據給定的相位,你可以指定在從一個相位過渡到另一個相位時使用的動畫。以下是一個例子:

.phaseAnimator([ false, true ]) { content, phase in
    content
        .scaleEffect(phase ? 1.0 : 0.5)
        .foregroundStyle(phase ? .indigo : .blue)
        .rotation3DEffect(
            phase ? .degrees(720) : .zero,
                                  axis: (x: 0.0, y: 1.0, z: 0.0)
        )
} animation: { phase in
    switch phase {
    case true: .smooth.speed(0.2)
    case false: .spring
    }
}

使用枚舉(Enum)來定義多步驟動畫

在前面的例子中,動畫只包含了兩個相位:falsetrue。然而,在更複雜的動畫中,通常涉及多個步驟或相位。在這種情況下,使用枚舉是一種很好的方式來定義動畫的步驟列表。

圖 54.3. 多步驟動畫
圖 54.3. 多步驟動畫

讓我們看看一個例子,動畫一個表情符號圖標,包含以下步驟:

  1. 初始狀態下,表情符號圖標居中在螢幕上。
  2. 它以50%的比例放大並自行旋轉720度。
  3. 接下來,它同時向上移動250個點,並以20%的比例縮小。
  4. 然後,它向下移動450個點。在下降的過程中,它以360度自行旋轉並以50%的比例縮小。
  5. 最後,它返回到原始位置。

有了這些步驟,我們可以為表情符號圖標創建一個動態動畫。

要實現這個多步驟動畫,我們可以像這樣定義一個枚舉:

enum Phase: CaseIterable {
    case initial
    case rotate
    case jump
    case fall

    var scale: Double {
        switch self {
        case .initial: 1.0
        case .rotate: 1.5
        case .jump: 0.8
        case .fall: 0.5
        }
    }

    var angle: Angle {
        switch self {
        case .initial, .jump: Angle(degrees: 0)
        case .rotate: Angle(degrees: 720)
        case .fall: Angle(degrees: 360)
        }
    }

    var offset: Double {
        switch self {
        case .initial, .rotate: 0
        case .jump: -250.0
        case .fall: 450.0
        }
    }
}

在這個枚舉中,我們有四個情況,代表動畫的不同步驟。在每個相位中,我們對表情符號圖標執行縮放、旋轉或移動操作。為了實現這一點,我們為每個動作定義了三個計算屬性。在每個屬性內部,我們指定了特定動畫相位或步驟的值。

例如,在「旋轉」相位中,表情符號應該放大50%並旋轉720度。scale 屬性返回1.5,angle 屬性返回 Angle(degrees: 720)

有了 Phase 枚舉,我們現在可以輕鬆地使用相位動畫器來對表情符號進行動畫,如下所示:

Text("🐻")
    .font(.system(size: 100))
    .phaseAnimator(Phase.allCases) { content, phase in
        content
            .scaleEffect(phase.scale)
            .rotationEffect(phase.angle)
            .offset(y: phase.offset)

    } animation: { phase in
        switch phase {
        case .initial: .bouncy
        case .rotate: .smooth
        case .jump: .snappy
        case .fall: .interactiveSpring
        }
    }

Phase.allCases 會自動通知相位動畫器可用的相位。根據給定的相位,表情符號圖標會根據計算出的值進行縮放、旋轉和移動。

要自定義動畫,我們可以為不同的相位指定特定的動畫,例如 snappy,而不是使用預設的動畫。

使用觸發器(Trigger)

目前,相位動畫器會自動啟動動畫並無限重複播放。然而,有些情況下,你可能更喜歡手動觸發動畫。在這種情況下,你可以通過在相位動畫器的 trigger 參數中指定所需的條件來定義自己的標準。

例如,當用戶點擊表情符號時,應該觸發表情符號的動畫。你可以首先聲明一個狀態變數,如下所示:

@State private var startAnimation = false

接下來,你通過添加 trigger 參數來更新 phaseAnimator 修飾器:

.phaseAnimator(Phase.allCases, trigger: startAnimation, content: { content, phase in
    content
        .scaleEffect(phase.scale)
        .rotationEffect(phase.angle)
        .offset(y: phase.offset)
}, animation: { phase in
    switch phase {
    case .initial: .bouncy
    case .rotate: .smooth
    case .jump: .snappy
    case .fall: .interactiveSpring
    }
})

修改程式碼之後,只有當 startAnimation 的值從 false 切換為 true 時,動畫才會觸發。為此,將 onTapGesture 修飾器附加到 Text 視圖上即可。

.onTapGesture {
    startAnimation.toggle()
}

當使用者點擊表情符號時,我們切換 startAnimation 的值。這將觸發多步驟動畫。

總結

引入 PhaseAnimator 大大簡化了創建多步驟動畫的過程。通過使用枚舉來定義動畫的每個步驟應該發生的變化,你只需幾行程式碼就可以創建出動態而引人入勝的動畫效果。SwiftUI 的 PhaseAnimator 與其他有用的功能一起,為你處理了繁重的工作,讓開發者能夠專注於創建令人印象深刻的動畫,而無需煩惱任何麻煩事。

您可以在此處下載演示項目作為參考: