利用 SwiftUI 一步步構建漂亮的彩色紙屑效果


本篇原文(標題:Creating Confetti Particle Effects Using SwiftUI)刊登於作者 Medium,由 Sarah 所著,並授權翻譯及轉載。

編者備註:在這篇文章中,我們會利用 SwiftUI,帶大家一步步構建漂亮的彩色紙屑效果

首先,讓我們建立一個 GeometryEffect 型別的結構。

我們需要定義紙屑的時間、速度和方向。讓我們把速度變數設置為 20 到 200 之間的隨機數值 (randomized number),如果使用較小的數字範圍(例如:10 … 30),紙屑散落就不太分散和劇烈。

讓我們看看下面的兩張圖片,左邊的速度設置為 20 到 30 之間,而右邊的速度就設置了成 20 到 100。

speed-range-1
speed-range-2

至於方向,我們可以使用 -pi 和 pi 之間的數值,如此一來,紙屑就會以完整的圓型散開。

如果我們把數值設置為 0 和 pi 之間,紙屑就會以半圓向下散開;而如果我們設置為 -pi 和 0 之間,就會達到相反的效果。

我們可以利用 animatableData 來增加時間的數值。animatableData 是一個特別的變數,如果我們改掉了它的名字,動畫就無法執行了。如果你我們刪除了 animatableData,紙屑就會固定某個位置。

animatabledata-1

接著,我們會實作 effectValue 函數,以遵從 GeometryEffect 協定。

我們需要弄清楚紙屑的移動 (translation)/位置 (position)。要計算紙屑的移動,我們可以利用速度、時間、加上方向的餘弦 (cosine) 及正弦 (sine) 函數,再利用 affineTranslation 來移動我們在以 graphic context 繪畫的物件。

利用我們移動的數值來創建 CGAffineTransform,然後利用 CGAffineTransform 回傳一個 ProjectionTransform

struct FireworkParticlesGeometryEffect : GeometryEffect {
    var time : Double
    var speed = Double.random(in: 20 ... 200)
    var direction = Double.random(in: -Double.pi ...  Double.pi)
    
    var animatableData: Double {
        get { time }
        set { time = newValue }
    }
    func effectValue(size: CGSize) -> ProjectionTransform {
        let xTranslation = speed * cos(direction) * time
        let yTranslation = speed * sin(direction) * time
        let affineTranslation =  CGAffineTransform(translationX: xTranslation, y: yTranslation)
        return ProjectionTransform(affineTranslation)
    }
}

紙屑修飾符

然後,讓我們建立一個視圖修飾符 (view modifier),來以迴圈 (loop) 來重複這個內容。在以下的程式碼中,你可以看到我把迴圈設定為 80,而你可以根據自己想要有多少紙屑來決定數值。

我們可以在這裡,添加任何想要加到紙屑上的修飾符。不過最重要的,就是要添加有客製化幾何效果的 .modifier(),包括我們在前文創建了的修飾符,以及下面程式碼可見的 .scaleEffect()opacity()

最後,讓我們在嵌入迴圈的 Zstack 中添加 onAppear(),在 onAppear() 中,我們可按時間和比例 (scale) 的數值來設置動畫。至於不透明度 (opacity),我們想設置紙屑在動畫完結時淡出。

struct ParticlesModifier: ViewModifier {
    @State var time = 0.0
    @State var scale = 0.1
    let duration = 5.0
    
    func body(content: Content) -> some View {
        ZStack {
            ForEach(0..<80, id: \.self) { index in
                content
                    .hueRotation(Angle(degrees: time * 80))
                    .scaleEffect(scale)
                    .modifier(FireworkParticlesGeometryEffect(time: time))
                    .opacity(((duration-time) / duration))
            }
        }
        .onAppear {
            withAnimation (.easeOut(duration: duration)) {
                self.time = duration
                self.scale = 1.0
            }
        }
    }
}

讓我們看看以下的對比圖像。左邊 duration 的數值是 5,而右邊的則是 15。你可以看到兩個設定對紙屑的大小和不透明度的分別。

ContentView

接下來,讓我們在 View 型別的結構內,把 ParticleModifier 添加到任何圖形或圖像。

在以下的程式碼中,我用了圓形 (circle) 為紙屑的圖形。另外,我也為其位置添加了一個 offset。

struct ContentView: View {
    var body: some View {
        ZStack {
            Circle()
                .fill(Color.blue)
                .frame(width: 12, height: 12)
                .modifier(ParticlesModifier())
                .offset(x: -100, y : -50)
            
            Circle()
                .fill(Color.red)
                .frame(width: 12, height: 12)
                .modifier(ParticlesModifier())
                .offset(x: 60, y : 70)
        }
    }
}
swiftui-confetti-particle

本篇文章到此為止,謝謝你的閱讀。

本篇原文(標題:Creating Confetti Particle Effects Using SwiftUI)刊登於作者 Medium,由 Sarah 所著,並授權翻譯及轉載。

作者簡介:Sarah,App 開發者,熱愛科技。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。


此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。

blog comments powered by Disqus
Shares
Share This