首先,讓我們建立一個 GeometryEffect
型別的結構。
我們需要定義紙屑的時間、速度和方向。讓我們把速度變數設置為 20 到 200 之間的隨機數值 (randomized number),如果使用較小的數字範圍(例如:10 … 30),紙屑散落就不太分散和劇烈。
讓我們看看下面的兩張圖片,左邊的速度設置為 20 到 30 之間,而右邊的速度就設置了成 20 到 100。
至於方向,我們可以使用 -pi 和 pi 之間的數值,如此一來,紙屑就會以完整的圓型散開。
如果我們把數值設置為 0 和 pi 之間,紙屑就會以半圓向下散開;而如果我們設置為 -pi 和 0 之間,就會達到相反的效果。
我們可以利用 animatableData
來增加時間的數值。animatableData
是一個特別的變數,如果我們改掉了它的名字,動畫就無法執行了。如果你我們刪除了 animatableData
,紙屑就會固定某個位置。
接著,我們會實作 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)
}
}
}
本篇文章到此為止,謝謝你的閱讀。