昨天入坑了「邏輯思維」團隊的「得到App」,存了錢進去以後發現在「帳戶餘額」介面上的「餘額」有動畫效果,
而之前在「餘額寶」的App中也有看到類似的動畫效果,給App增加了不少樂趣,感覺就像玩遊戲,錢一直在漲的感覺 (=^W^=)
會跳動的數字
上面這張GIF就是實作後的動畫效果,如果我的帳號這樣輕鬆地增加餘額就好了(=^O^=)
往下看之前,也可以先思考看看,如果今天遇到這樣的需求,會如何去實現這樣的效果。
數字變化的過程
如果今天PM告訴我們需要一個「從0過渡到1024的動畫效果」,我想有些人會覺得很簡單,直接做一個循環,在畫面上顯示1、2、3、4…1024。
但其實這種動畫通常不會顯示很久,測試下來感覺2秒就開始考驗耐性了。
所以其實應該是需要寫成像「進度條」那樣,根據當前進度跳躍式的重新渲染畫面(如0、124、258…1024),這樣一定比1、2、3、4…1024每一個數字都要進行一次渲染來得有效率。
畢竟應該沒有PM會說,「我們希望用戶可以在1秒內看到每一個數字」…
兩個對外的動畫方法
定義「初始數字」、「最終數字」、「動畫時間」、啟動「Timer」。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
func count(from: Float, to: Float, duration: TimeInterval? = nil) { startingValue = from destinationValue = to timer?.invalidate() timer = nil if (duration == 0.0) { // No animation setTextValue(value: to) return } progress = 0.0 totalTime = duration ?? animationDuration lastUpdateTime = Date.timeIntervalSinceReferenceDate addDisplayLink() } func countFromCurrent(to: Float, duration: TimeInterval? = nil) { count(from: currentValue, to: to, duration: duration ?? nil) } |
用CADisplayLink代替Timer
Timer所在的runloop中因為需要處理很多事物,所以它最小週期大約在50~100ms之間(官方描述),也就是1秒內大約能處理20次,這樣不容易達到60FPS的順暢感,
1 2 3 4 5 6 7 |
timer = CADisplayLink(target: self, selector: #selector(self.updateValue(timer:))) // 相當於Timer的fire(),開始執行 timer?.add(to: .main, forMode: .defaultRunLoopMode) // 防止tableView等畫面拖動時的影響。 timer?.add(to: .main, forMode: .UITrackingRunLoopMode) |
timer會呼叫「updateValue」方法,來更新當前數字的「進度」,並且通過settextValue()來改變UILabel中的text。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@objc fileprivate func updateValue(timer: Timer) { let now: TimeInterval = Date.timeIntervalSinceReferenceDate progress += now - lastUpdateTime lastUpdateTime = now if progress >= totalTime { self.timer?.invalidate() self.timer = nil progress = totalTime } setTextValue(value: currentValue) } // update UILabel.text fileprivate func setTextValue(value: Float) { text = String(format: "$ %.1f", value) } |
當前餘額變化進度的計算方式
以時間為單位,如果progress已經達到/超過動畫時間,就直接顯示最終的數字。
1 2 3 4 |
fileprivate var currentValue: Float { if progress >= totalTime { return destinationValue } return startingValue + Float(progress / totalTime) * (destinationValue - startingValue) } |
參考和推薦
- 本文中的例子,請參考Github上的 Swift實現UILabel動畫效果。
- Apple – Timer
分享文章:「Swift實現UILabel中數字動畫效果」 – https://ios.devdon.com/archives/507
这个方法不够优雅,最好的方式是用CALayer的自定义动画
是的,可以在做得更好:D