iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 1
1

模仿「得到」App 的儲值動畫

得到

這是「邏輯思維」團隊的「得到」App,把錢存進去會在「我的帳戶」裡面看到一個餘額的數字動畫。

而之前在「餘額寶」的App中也有看到類似的動畫效果,給App增加了不少樂趣,這次打算實作看看。


BankBalance

BankBalance

數字變化的過程

如果今天PM告訴我們需要一個「從0過渡到1024的動畫效果」,我想有些人會覺得很簡單,直接做一個循環,在畫面上顯示1、2、3、4...1024。
但其實這種動畫通常不會顯示很久,測試下來感覺2秒就開始考驗耐性了。

所以其實應該是需要寫成像「進度條」那樣,根據當前進度跳躍式的重新渲染畫面(如0、124、258...1024),這樣一定比1、2、3、4...1024每一個數字都要進行一次渲染來得有效率。
畢竟應該沒有PM會說,「我們希望用戶可以在1秒內看到每一個數字」...


兩個對外的動畫方法

定義「初始數字」、「最終數字」、「動畫時間」、啟動「Timer」

    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的順暢感。

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中的

    @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已經達到/超過動畫時間,就直接顯示最終的數字。

    fileprivate var currentValue: Float {
        if progress >= totalTime { return destinationValue }
        return startingValue + Float(progress / totalTime) * (destinationValue - startingValue)
    }

筆記

  • TODO:嘗試通過 UILayer 做動畫
  • TODO:嘗試將功能封裝起來,支持除了 Float 以外的動畫方式(考慮范型)

參考


下一篇
StackViewAnimation
系列文
iOS Swift x Layout x Animation x Transition30

尚未有邦友留言

立即登入留言