前面写了一个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)
}