動畫實作

利用 SwiftUI 構建一個像 Instagram 直播的按讚按鈕動畫

Apple 的 SwiftUI 為開發者提供了一個輕巧易用的工具,來創建使用者界面。在這篇教學文章中,Sarah 會帶大家在 SwiftUI 使用 GeometryEffect 和 ViewModifier,一步步製作出一個類似 Instagram 直播的按讚按鈕動畫!
利用 SwiftUI 構建一個像 Instagram 直播的按讚按鈕動畫
利用 SwiftUI 構建一個像 Instagram 直播的按讚按鈕動畫
In: 動畫實作, SwiftUI 框架
本篇原文(標題:Floating Hearts Animation in SwiftUI)刊登於作者 Medium,由 Sarah 所著,並授權翻譯及轉載。

在這篇教學文章中,我們會使用 GeometryEffectViewModifier,來製作類似 Instagram 的按讚按鈕動畫。我們也可以使用 SKSceneSpriteView 來實作類似的效果。

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。

本篇原文(標題:Floating Hearts Animation in SwiftUI)刊登於作者 Medium,由 Sarah 所著,並授權翻譯及轉載。
作者簡介:Sarah,App 開發者,熱愛科技。
譯者簡介:Kelly Chan-AppCoda 編輯小姐。

作者
AppCoda 編輯團隊
此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。
評論
更多來自 AppCoda 中文版
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。