在這篇教學文章中,我們會使用 GeometryEffect
和 ViewModifier
,來製作類似 Instagram 的按讚按鈕動畫。我們也可以使用 SKScene
和 SpriteView
來實作類似的效果。
SwiftUI:心心的 GeometryEffect
首先,我們需要創建一個幾何效果,來隨時間改變心心的 x 和 y direction 或 position。x position 會將心心拉近、置中、或偏離中心,其數值介於 -0.05 和 0.05 之間;而 y direction 就是利用 sin 和介於 -pi 和 0 之間的數值來計算。如果我們的 like 按鈕很大,這些輕微的移動可能不夠,這樣就會需要調整 x 和 y 值。
我們可以按照以下步驟,來回傳正確的投影轉換 (Projection Transform):
- 添加一個 speed 變數,並給它一個 100 到 200 的隨機值。
- 添加型別為 Double 的 time 變數和 animatableData 變數。animatableData 變數會從零開始計時到特定時間,然後我們會傳遞 ViewModifier(我們稍後會創建 ViewModifier)。
- 建立一個 xTranslation 變數,將 xDirection 和 speed 相乘。
- 建立一個 yTranslation 變數,將 sin(yDirection)、speed、和 time 相乘。
- 利用 xTranslation 和 yTranslation,建立並回傳一個 CGAffineTransform。
struct LikesGeometryEffect : GeometryEffect {
var time : Double
var speed = Double.random(in: 100 ... 200)
var xDirection = Double.random(in: -0.05 ... 0.05)
var yDirection = Double.random(in: -Double.pi ... 0)
var animatableData: Double {
get { time }
set { time = newValue }
}
func effectValue(size: CGSize) -> ProjectionTransform {
let xTranslation = speed * xDirection
let yTranslation = speed * sin(yDirection) * time
let affineTranslation = CGAffineTransform(translationX: xTranslation, y: yTranslation)
return ProjectionTransform(affineTranslation)
}
}
點擊來給讚的 ViewModifier
在這個部分,我們會建立一個包含內容 (content) 的 ViewModifier
。在這裡,我們會使用 WithAnimation
更改 time 值,並傳遞給 GeometryEffect
修飾符 (modifier)。
要實作心心淡出的效果,我們要先添加一個 opacity 修飾符,並讓修飾符在時間等於動畫設置的時間時設置為 0。讓我們看看以下程式碼:
struct LikeTapModifier: ViewModifier {
@State var time = 0.0
let duration = 1.0
func body(content: Content) -> some View {
ZStack {
content
.foregroundColor(.red)
.modifier(LikesGeometryEffect(time: time))
.opacity(time == 1 ? 0 : 1)
}
.onAppear {
withAnimation (.easeOut(duration: duration)) {
self.time = duration
}
}
}
}
ContentView 的按讚按鈕
在符號方面,我們會用 heart
作按讚按鈕,而按讚後的心心就會用 heart.fill
。我們也會用一個變數來紀錄按讚數量。
讓我們為按讚數量建立一個變數,每當使用者點擊 按讚按鈕時,按讚數量就會增加。把 heart.fill
圖像嵌入遍歷按讚數量的 ForEach
循環中。
最後,把 LikeTapModifier
添加循環內的圖像中。
struct ContentView: View {
@State var likes = 0
func likeAction () {
likes += 1
}
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
VStack {
Spacer()
HStack {
Spacer()
ZStack {
Image(systemName: "heart")
.resizable()
.frame(width: 30, height: 30)
.padding()
.onTapGesture {
likeAction()
}
ForEach (0..<likes, id: \.self){ _ in
Image(systemName: "heart.fill")
.resizable()
.frame(width: 30, height: 30)
.modifier(LikeTapModifier())
.padding()
}
}.foregroundColor(.white.opacity(0.5))
}
}
}
}
}
提升效能
在 debug navigator 中,我們可以看到記憶體 (Memory) 不斷增加。要解決這個問題,我們可以把 likes 從 int 更改為可識別的結構 (identifiable struct),然後迴圈遍歷程式碼。
我們可以利用 onChange
控制按讚數量,避免心心不斷增加。讓我們看看以下的程式碼:
struct ContentView: View {
@State var likes :[LikeView] = []
func likeAction () {
likes += [LikeView()]
}
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
ZStack {
Image(systemName: "heart")
.resizable()
.frame(width: 50, height: 50)
.padding()
.onTapGesture {
likeAction()
}
likesPile()
.offset(y: -30)
ForEach (likes) { like in
like.image.resizable()
.frame(width: 50, height: 50)
.modifier(LikeTapModifier())
.padding()
.id(like.id)
}.onChange(of: likes) { newValue in
if likes.count > 5 {
likes.removeFirst()
}
}
}.foregroundColor(.white.opacity(0.5))
.offset(x: 0, y: 60)
}
}
}
struct LikeView : Identifiable, Equatable {
let image = Image(systemName: "heart.fill")
let id = UUID()
}
這篇教學文章到此為止,謝謝你的閱讀。
特別鳴謝 Anupam Chugh。