您好,登錄后才能下訂單哦!
SwiftUI 中實現創建反彈動畫?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
SwiftUI 中的動畫
在寫動畫之前呢先簡單回顧一下 SwiftUI 中動畫的幾個要點:
反彈動畫
反彈動畫屬于“起始點和終止點相等”的動畫,所以不能夠通過 SwiftUI 中內建的動畫來實現(因為這個 view 從結果來看沒有發生變化)
我們先來構建反彈動畫修飾器的框架如下:
struct Bounce: AnimatableModifier { var animCount: CGFloat = 0 var amplitude: CGFloat = 10 // 振幅 var animatableData: CGFloat { get { animCount } set { animCount = newValue } } func body(content: Content) -> some View { // change view to animate } }
下面一步一步來
實現動畫曲線
實現 body 方法,好讓 times 每次增加時 view 能以反彈的方式進行動畫。一次反彈就是 view 向上彈起又落下,下面是動畫曲線大致的樣子:
三角函數是 y = -abs(sin x)
,所以 body 方法這樣實現:
struct Bounce: AnimatableModifier { ... func body(content: Content) -> some View { let offset: CGFloat = -abs(sin(animCount * .pi * 2) * amplitude) return content.offset(y: offset) } }
測試代碼:
struct BouncingView: View { @State var taps = 0 var body: some View { Button(action: { withAnimation(Animation.easeInOut(duration: 1)) { taps += 1 } }, label: { RoundedRectangle(cornerRadius: 15) .modifier(Bounce(animCount: CGFloat(taps))) }) .frame(width: 100, height: 100) } }
點擊按鈕,就能彈兩次了~~
這個 ​ @State var taps
​ 其實并沒有什么實際的意義,只是為了觸發動畫。
因為 SwiftUI 里只有 View 的狀態發生變化才會觸發動畫,而無法通過某個事件來觸發;而我們的動畫是一個初始狀態和結束狀態相等的情況,并沒有狀態的變化,所以這里強行把點擊的次數作為 View 狀態的變化來觸發動畫。
我找了好久有沒有更優雅的方式來解決這個問題,然而并沒有找到 = =b
View 擴展
暴露給外面的 ​ animCount
​ 應該是一個 Int 才對,那么就增加一個 ​ ​animValue
​ 來代替它
struct Bounce: AnimatableModifier { let animCount: Int var animValue: CGFloat var amplitude: CGFloat = 10 // 振幅 init(animCount: Int) { self.animCount = animCount self.animValue = CGFloat(animCount) } var animatableData: CGFloat { get { animValue } set { animValue = newValue } } func body(content: Content) -> some View { let offset: CGFloat = -abs(sin(animValue * .pi * 2) * amplitude) return content.offset(y: offset) } }
因為 ​ ​animValue
​ 被隱藏起來了,所以需要初始化方法
為了方便使用,再添加一個 View 的擴展方法:
extension View { func bounce(animCount: Int) -> some View { self.modifier(Bounce(animCount: animCount)) } }
增加彈性
現在雖然能彈了,但是相對還比較生硬,就想在 View “落地“后再給它增加振幅逐步衰減的幾次反彈;一開始嘗試了簡單的減半反彈,實驗證明觀感更差,看起來有點難受。
我們實際生活中的反彈的振幅變化應該是符合 阻尼正弦波 的,參考鏈接里的公式,修改如下:
struct Bounce: AnimatableModifier { let animCount: Int var animValue: CGFloat var amplitude: CGFloat // 振幅 var bouncingTimes: Int init(animCount: Int, amplitude: CGFloat = 10, bouncingTimes: Int = 3) { self.animCount = animCount self.animValue = CGFloat(animCount) self.amplitude = amplitude self.bouncingTimes = bouncingTimes } var animatableData: CGFloat { get { animValue } set { animValue = newValue } } func body(content: Content) -> some View { let t = animValue - CGFloat(animCount) let offset: CGFloat = -abs(pow(CGFloat(M_E), -t) * sin(t * .pi * CGFloat(bouncingTimes)) * amplitude) return content.offset(y: offset) } } extension View { func bounce(animCount: Int, amplitude: CGFloat = 10, bouncingTimes: Int = 3) -> some View { self.modifier(Bounce(animCount: animCount, amplitude: amplitude, bouncingTimes: bouncingTimes)) } }
這里我們還增加了 ​ bouncingTimes
​ 作為彈跳次數的參數,振幅也作為參數開放給用戶;
由于阻尼正弦波是逐步衰減的,為了每次點擊的彈跳都一樣,所以得用 ​ ​animValue - CGFloat(animCount)
​ 作為阻尼正弦波函數的參數。
測試代碼修改如下:
struct BouncingView: View { @State var taps = 0 var body: some View { Button(action: { withAnimation(Animation.linear(duration: 1)) { taps += 1 } }, label: { RoundedRectangle(cornerRadius: 15) .bounce(animCount: taps) }) .frame(width: 100, height: 100) } }
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。