版权所有,禁止匿名转载;禁止商业使用。
前面写了一个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) }