CAShapeLayer 打造动态图表

CAShapeLayer 追本溯源

CAShapeLayer 是 QuartzCore 框架下非常重要的一个类,利用它可以实现各种图形的绘制类动画效果。

CAShapeLayer 是 iOS 框架下的核心动画部分,Shape 为形状的意思,描述了当前动画的特点,可以实现各类形状的绘制。

Layer 表明当前动画并非直接作用于 UIView 显示层上,而是作用于 Layer 内容层上。

动态折线图表

创建基于 UIView 的 LineChartView.swift 类

let PNGreen:UIColor = UIColor(red: 77.0/255.0, green: 186.0/255.0, blue: 122.0/255.0, alpha: 1.0)
var chartLine:CAShapeLayer = CAShapeLayer()
var pathAnimation:CABasicAnimation = CABasicAnimation()
override init(frame: CGRect) {
   super.init(frame: frame)
   self.backgroundColor = UIColor.white // 背景颜色为白色
   self.clipsToBounds = true // 超出部分的裁剪属性
   chartLine.lineCap = kCALineCapRound // 连接处的线条类型
   chartLine.lineJoin = kCALineJoinRound // 拐角处的线条类型
   chartLine.fillColor = UIColor.white.cgColor // 折线的填充颜色
   chartLine.lineWidth = 10.0 // 线条宽度
   chartLine.strokeEnd = 0.0 // 线条一点点进行绘制
   self.layer.addSublayer(chartLine)
}
required init?(coder aDecoder: NSCoder) {
   fatalError("init(coder:) has not been implemented")
}
func drawLineChart(){
   chartLine.strokeEnd = 1.0
   chartLine.add(pathAnimation, forKey: nil)

}
override func draw(_ rect: CGRect) {
   let line:UIBezierPath = UIBezierPath()
   line.lineWidth = 10.0
   line.lineCapStyle = CGLineCap.round
   line.lineJoinStyle = CGLineJoin.round
   line.move(to: CGPoint(x: 70, y: 260))
   line.addLine(to: CGPoint(x: 140, y: 100))
   line.addLine(to: CGPoint(x: 210, y: 240))
   line.addLine(to: CGPoint(x: 280, y: 170))
   line.addLine(to: CGPoint(x: 350, y: 220))
   chartLine.path = line.cgPath
   chartLine.strokeColor = PNGreen.cgColor
   
   pathAnimation.keyPath = "strokeEnd"
   
   pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
   pathAnimation.fromValue = 0.0
   pathAnimation.toValue = 1.0
   pathAnimation.autoreverses = false
   pathAnimation.duration = 2.0
   
}

动态柱状图动画

创建基于 UIView 的 BarChartView 类

var chartLine:CAShapeLayer = CAShapeLayer()
var pathAnimation:CABasicAnimation = CABasicAnimation()
override init(frame: CGRect) {
   super.init(frame: frame)
   self.backgroundColor = UIColor.white
   self.clipsToBounds = true
   chartLine.lineCap = kCALineCapSquare
   chartLine.lineJoin = kCALineJoinRound
   chartLine.fillColor = UIColor.gray.cgColor
   chartLine.lineWidth = 30.0
   chartLine.strokeEnd = 0.0
   self.layer.addSublayer(chartLine)
}

required init?(coder aDecoder: NSCoder) {
   fatalError("init(coder:) has not been implemented")
}
func drawLineChart(){
   chartLine.strokeEnd = 1.0
   chartLine.add(pathAnimation, forKey: nil)
}
override func draw(_ rect: CGRect) {
   let line:UIBezierPath = UIBezierPath()
   line.lineWidth = 30.0
   line.lineCapStyle = CGLineCap.square
   line.lineJoinStyle = CGLineJoin.round
   for i in 0...4 {
       let x:CGFloat = CGFloat(60+70*i)
       let y:CGFloat = CGFloat(100+20*i)
       line.move(to: CGPoint(x: x, y: 215))
       line.addLine(to: CGPoint(x: x, y: y))
   }
   chartLine.path = line.cgPath
   chartLine.strokeColor = PNGreen.cgColor
   pathAnimation.keyPath = "strokeEnd"
   pathAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
   pathAnimation.fromValue = 0.0
   pathAnimation.toValue = 1.0
   pathAnimation.autoreverses = false
   pathAnimation.duration = 2.0
   
}

调用

class ViewController: UIViewController {
    var lineChartView1:LineChartView?
    var barChartView1:BarChartView?
    override func viewDidLoad() {
        super.viewDidLoad()
        
        lineChartView1 = LineChartView(frame: self.view.bounds)
        self.view.addSubview(lineChartView1!)
        
        barChartView1 = BarChartView(frame: CGRect(x: 0, y: self.view.bounds.height/2.0, width: self.view.bounds.width, height: self.view.bounds.height))
        self.view.addSubview(barChartView1!)
        
        self.addDrawChartButton()
        self.addAxes()
    }
    func addAxes(){
        // LineChart
        for i in 1...5{
            let xAxesTitle:String = "SEP"+"\(i)"
            let xAxesLabel:UILabel = UILabel(frame: CGRect(x: 50+(CGFloat(i)-1)*70,y: 300, width: 50, height: 20))
            xAxesLabel.text = xAxesTitle
            self.view.addSubview(xAxesLabel)
        }
        for i in 0...5{
            let yAxesTitle:String = "\(10-i*2)"
            let yAxesLabel:UILabel = UILabel(frame: CGRect(x: 20,y: 120+(CGFloat(i)-1)*35, width: 20, height: 20))
            yAxesLabel.text = yAxesTitle
            self.view.addSubview(yAxesLabel)
        }
        // BarChart
        for i in 1...5{
            let xAxesTitle:String = "SEP"+"\(i)"
            let xAxesLabel:UILabel = UILabel(frame: CGRect(x: 40+(CGFloat(i)-1)*70,y: 600, width: 50, height: 20))
            xAxesLabel.text = xAxesTitle
            self.view.addSubview(xAxesLabel)
        }
    }
    func addDrawChartButton(){
        let bt_line:UIButton = UIButton()
        bt_line.frame = CGRect(x: (self.view.frame.size.width-100)/2, y: 20, width: 100, height: 50)
        bt_line.setTitle("Line Chart", for: UIControlState())
        bt_line.setTitleColor(PNGreen, for: UIControlState())
        bt_line.addTarget(self, action: #selector(ViewController.drawChart), for: UIControlEvents.touchUpInside)
        self.view.addSubview(bt_line)
        
    }
    
    func drawChart(){
        lineChartView1!.drawLineChart()
        barChartView1!.drawLineChart()
    }
}

效果如下:

2017062514983966923811.gif

上文代码:点击查看

CAReplicatorLayer:图层复制效果

CAReplicatorLayer 主要由三个部分组成:CA、 Replicator、 Layer。CA 为 CoreAnimation 的缩写,表明我们当前的动画使用的是 iOS 框架下的核心动画框架, Replicator 表示该图层可以用于图层的快速复制。 Layer 表明当前动画并非直接作用于 UIView 显示层上,而是作用在 Layer 内容层上。

恒星旋转动画实现

let UISCREEN_WIDTH = UIScreen.main.bounds.size.width
let UISCREEN_HEIGHT = UIScreen.main.bounds.size.height
var replicatorLayer:CAReplicatorLayer = CAReplicatorLayer()
var iv_earth:UIImageView?
override func viewDidLoad() {
   super.viewDidLoad()
   
   let background:UIImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: UISCREEN_WIDTH, height: UISCREEN_HEIGHT))
   background.image = UIImage(named: "background.jpg")
   self.view.addSubview(background)
   
   iv_earth = UIImageView(frame: CGRect(x: (UISCREEN_WIDTH-50)/2+150, y: (UISCREEN_HEIGHT-50)/2, width: 50, height: 50))
   iv_earth!.image = UIImage(named: "earth.png")
   
   let iv_sun = UIImageView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
   iv_sun.center = self.view.center
   iv_sun.image = UIImage(named: "sun.png")
   
   replicatorLayer.addSublayer(iv_earth!.layer)
   replicatorLayer.addSublayer(iv_sun.layer)
}
override func viewWillAppear(_ animated: Bool) {
   let path:UIBezierPath = UIBezierPath()
   path.addArc(withCenter: CGPoint(x: self.view.center.x, y: self.view.center.y), radius: 150, startAngle: 0, endAngle: CGFloat(M_PI*2), clockwise: true)
   path.close()
   let animation:CAKeyframeAnimation = CAKeyframeAnimation(keyPath:"position")
   animation.path = path.cgPath
   animation.duration = 10
   animation.repeatCount = MAXFLOAT
   
   replicatorLayer.instanceCount = 100
   replicatorLayer.instanceDelay = 0.2
   self.view.layer.addSublayer(replicatorLayer)
   
   iv_earth?.layer.add(animation, forKey: nil)
}

效果如下:

201706261498481874461.gif

音量跳动动画

let replicatorLayer:CAReplicatorLayer = CAReplicatorLayer()
replicatorLayer.frame = CGRect(x: 0, y: 0, width: 414, height: 200)
replicatorLayer.instanceCount = 20;
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(20, 0, 0)
replicatorLayer.instanceDelay = 0.2
replicatorLayer.masksToBounds = true
replicatorLayer.backgroundColor = UIColor.black.cgColor;
   
let layer = CALayer()
layer.frame = CGRect(x: 14, y: 200, width: 10, height: 100)
layer.backgroundColor = UIColor.red.cgColor

replicatorLayer.addSublayer(layer)
self.view.layer.addSublayer(replicatorLayer)
   
let animation:CABasicAnimation = CABasicAnimation()
animation.keyPath = "position.y"
animation.duration = 0.5
animation.fromValue = 200
animation.toValue = 180
animation.autoreverses = true
animation.repeatCount = MAXFLOAT
layer.add(animation, forKey: nil)

效果如下:

20170626149848216448994.gif

上文代码:点击查看