0%

iOS开发:仿余额宝金额跳动效果

前面写了一个Android版,因为有属性动画,很简单几行代码就实现了。 iOS版复杂多了,当然也可能我没找到简单的方法。参考了开源项目 https://github.com/PigRiver/NumberJumpDemo,这个是用OC写的,我用Swift改写了一个版本。 说下原理:先确定使用的贝塞尔曲线, 确定两桢间的间隔,根据间隔和总时间计算得到总桢数,根据贝塞尔曲线算出每桢的时间和值,然后delay设置显示的string。

贝塞尔曲线可以在http://cubic-bezier.com/生成。我选了ease-in-out的效果,不过为了ease更明显,我改了下。

//存储贝塞尔曲线的点
import UIKit
class BezierPoint {
let x:Float!
let y:Float!
init(x:Float,y:Float) {
self.x = x
self.y = y
}
}

算点

import UIKit

class BezierCurve {
//根据t(位置,0-1)取点
class func PointOnCubicBezier(cp:[BezierPoint], t:Float) -> BezierPoint{
var ax,bx,cx:Float
var ay,by,cy:Float
var tSquared,tCubed:Float

    /*計算多項式係數*/
    
    cx = 3.0 * (cp\[1\].x - cp\[0\].x);
    bx = 3.0 * (cp\[2\].x - cp\[1\].x) - cx;
    ax = cp\[3\].x - cp\[0\].x - cx - bx;
    
    cy = 3.0 * (cp\[1\].y - cp\[0\].y);
    by = 3.0 * (cp\[2\].y - cp\[1\].y) - cy;
    ay = cp\[3\].y - cp\[0\].y - cy - by;
    /*計算位於參數值t的曲線點*/
    tSquared = t * t;
    tCubed = tSquared * t;
    var x = (ax * tCubed) + (bx * tSquared) + (cx * t) + cp\[0\].x;
    var y = (ay * tCubed) + (by * tSquared) + (cy * t) + cp\[0\].y;
    return BezierPoint(x: x, y: y)
}

}

使用CATextLayer来渲染

import UIKit

class CountLayer: CATextLayer {
//每帧间隔
let durationPerFrame:NSTimeInterval = 20.0;
//总时间
var durationTotal:NSTimeInterval = 1500.0
//开始值
var startNumber:Float!
//结束值
var endNumber:Float!
//总点数
var pointNumber:Int!
//当前画的点
var indexCurrent:Int
var lastTime:Float!
//开始点
let startPoint:BezierPoint = BezierPoint(x: 0, y: 0)
//两个bezier点,http://cubic-bezier.com/ 可以生成
let controlPoint1:BezierPoint = BezierPoint(x: 0.0, y: 0.78)
var controlPoint2:BezierPoint = BezierPoint(x: 0.0, y: 0.98)
//结束点,固定1,1
var endPoint:BezierPoint = BezierPoint(x: 1, y: 1)
//计算得到的所有点
var allPoints:NSMutableArray!

override init!() {
     indexCurrent = 0
    super.init()
    initDate()
}

required init(coder aDecoder: NSCoder) {
     indexCurrent = 0
    super.init(coder: aDecoder)
   initDate()
}

private func initDate() {
    allPoints = NSMutableArray()
   
    lastTime = 0
}

//动画跳动展示
func showNumberWithAnimation(duration:NSTimeInterval, startNumber:Float, endNumber:Float) {
    indexCurrent = 0
    lastTime = 0
    self.durationTotal = duration
    self.startNumber = startNumber
    self.endNumber = endNumber
    initBezierPoint()
    changeNumberBySelector()
}

//初始化h
private func initBezierPoint() {
    //总共多少点
    pointNumber = Int(ceil(durationTotal/durationPerFrame))
    var bezierPoints:\[BezierPoint\] = \[startPoint, controlPoint1, controlPoint2, endPoint\]
    allPoints.removeAllObjects()
    //读出每点的时间和值
    for index in 0 ..< pointNumber {
        var t = Float(index)/Float(pointNumber)
        var point:BezierPoint = BezierCurve.PointOnCubicBezier(bezierPoints, t: t)
        var durationTime = point.x * Float(durationTotal)
        var value:Float = point.y * (endNumber - startNumber) + startNumber;
        allPoints.addObject(NSArray(array: \[durationTime,value\]))
    }
    
    
}
func changeNumberBySelector() {
    if(indexCurrent >= pointNumber) {
        self.string = NSString(format: "%.2f", endNumber)
    }else{
        var currentPoint: NSArray = allPoints.objectAtIndex(indexCurrent) as NSArray
        var value = currentPoint.objectAtIndex(1) as Float
        var currentTime = currentPoint.objectAtIndex(0) as Float
        
        ++indexCurrent
        var timeDuration = currentTime - lastTime
        lastTime = currentTime;
        self.string = NSString(format: "%.2f", value)
        
        //延迟调用changeNumberBySelector方法,重新设string
        let delay = durationPerFrame * Double(NSEC\_PER\_MSEC)
        var time = dispatch\_time(DISPATCH\_TIME_NOW, Int64(delay))
        dispatch\_after(time, dispatch\_get\_main\_queue(), {
            NSThread.detachNewThreadSelector(Selector("changeNumberBySelector"), toTarget:self, withObject: nil)
        })

    }
}

}

使用:

//赚到的钱
var earnLayer:CountLayer!

override func viewDidLoad() {
    super.viewDidLoad()
           
    earnLayer = CountLayer()
    earnLayer.frame = CGRectMake(85, 37, 151, 80)
    earnLayer.string = "asdf"
    earnLayer.fontSize = 30
    earnLayer.contentsScale = 2
    earnLayer.alignmentMode = "center"
    topBgView.layer.addSublayer(earnLayer)
   
    
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

@IBAction func test(sender: AnyObject) {
    earnLayer.showNumberWithAnimation(1500, startNumber: 0.0, endNumber: 999999.65)
    
}