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)
  }


0 0