iOS 贝塞尔曲线路径动画 SVG快速实现(Swift版)

2019-02-25  本文已影响25人  老坛泡菜

本文将简单实现iOS快速路径绘制动画。

什么核心动画(Core Animation)、CAShapeLayer、UIBeizerPath等这些,这篇文章就不多说了。
反正看完这篇文章可以实现以下两种效果,虽然这两种效果很弱智,但高楼从地起嘛,懂了原理可以实现越来越复杂的动画组。Demo在最后可以下载,代码很烂,大神轻喷。



注:二维码效果来源:文章,因为原文里的部分功能无法重现,故在此重新写了个Demo。

案例一:快速实现路径动画

class ESSVGView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.white
        drawBezierPath()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func drawBezierPath() {
       
        //通过PaintCode生成的代码
        let pathPath = UIBezierPath()
        pathPath.move(to: CGPoint(x: 179.5, y: 36.5))
        pathPath.addCurve(to: CGPoint(x: 52.5, y: 118.5), controlPoint1: CGPoint(x: 95.5, y: 81.5), controlPoint2: CGPoint(x: 52.5, y: 118.5))
        pathPath.addLine(to: CGPoint(x: 263.5, y: 118.5))
        pathPath.addCurve(to: CGPoint(x: 75.5, y: 253.5), controlPoint1: CGPoint(x: 263.5, y: 118.5), controlPoint2: CGPoint(x: 4.5, y: 205.5))
        pathPath.addCurve(to: CGPoint(x: 263.5, y: 253.5), controlPoint1: CGPoint(x: 146.5, y: 301.5), controlPoint2: CGPoint(x: 263.5, y: 253.5))
        pathPath.addCurve(to: CGPoint(x: 75.5, y: 378.5), controlPoint1: CGPoint(x: 263.5, y: 253.5), controlPoint2: CGPoint(x: 222.5, y: 413.5))
        pathPath.addCurve(to: CGPoint(x: 134.5, y: 527.5), controlPoint1: CGPoint(x: -71.5, y: 343.5), controlPoint2: CGPoint(x: 134.5, y: 527.5))
        pathPath.addLine(to: CGPoint(x: 263.5, y: 404.5))
        
        //添加path到ShapeLayer
        let pathLayer = CAShapeLayer()
        pathLayer.frame = frame
        pathLayer.bounds = frame
        pathLayer.isGeometryFlipped = false
        pathLayer.path = pathPath.cgPath
        pathLayer.strokeColor = UIColor.red.cgColor
        pathLayer.fillColor = nil
        pathLayer.lineWidth = 3
        pathLayer.lineJoin = CAShapeLayerLineJoin.bevel
        layer.addSublayer(pathLayer)
        
        //添加动画
        let animation = CABasicAnimation(keyPath: "strokeEnd") //strokeEnd正常绘制效果,strokeStart逐渐消失效果
        animation.fromValue = 0 //初始值
        animation.toValue = 1 //结束值
        animation.repeatCount = 10 //重复次数
        animation.duration = 10 //动画时间
        pathLayer.add(animation, forKey: nil)
        
    }

}

4.接着去ViewController,添加到视图里就OK了。简单粗暴!

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let svg = ESSVGView(frame: view.frame)
        view.addSubview(svg)
    }

}

案例二:快速实现二维码动画


2.下载SVG格式文件



3.这里可以用文本编辑打开一下这个SVG文件,其实里面就是个XML,注意这些use就是二维码中一个个小方块。
每个方块x,y都知道了,width和height是固定值都是12。


4.接下来将SVG格式文件拖到工程,并用XML来解析这个文件

 override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.white
        let qrPath = Bundle.main.path(forResource: "qr", ofType: "svg")!
        let qrData = NSData(contentsOfFile: qrPath)
        let xmlParser = XMLParser(data: qrData! as Data)
        xmlParser.delegate = self
        xmlParser.parse()
    }

5.使用XML代理的2个方法进行处理

每当解析到一个新标签,这里就会被调用
 func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:])
整个 svg 文件解析完毕后,这里就会被调用
func parserDidEndDocument(_ parser: XMLParser)

6.最后我就直接把代码贴上来,关键地方都注释了干啥滴。

class ESXML: UIView {
    
    var rects = [CGRect]()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.white
        //解析SVG文件
        let qrPath = Bundle.main.path(forResource: "qr", ofType: "svg")!
        let qrData = NSData(contentsOfFile: qrPath)
        let xmlParser = XMLParser(data: qrData! as Data)
        xmlParser.delegate = self
        xmlParser.parse()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

extension ESXML: XMLParserDelegate {
    
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        if elementName == "use" {
            //但elementName等于use的时候,记录rect并添加的rects数组中
            let x = Double(attributeDict["x"]!)
            let y = Double(attributeDict["y"]!)
            let rect = CGRect(x: x!, y: y!, width: 12, height: 12)
            rects.append(rect)
        }else if elementName == "svg" {
            //elementName等于svg的时候,记录这个二维码大小
            let w = Double(attributeDict["width"]!)
            let h = Double(attributeDict["height"]!)
            bounds = CGRect(x: 0, y: 0, width: w!, height: h!)
        }
    }
    
    func parserDidEndDocument(_ parser: XMLParser) {
        //给layer添加阴影
        layer.shadowColor = UIColor.gray.cgColor
        layer.shadowRadius = 4
        layer.shadowOpacity = 1
        layer.shadowOffset = CGSize.zero
        
        //遍历rects数组,把每一个rect变为一个贝塞尔路径,并用一个CAShapeLayer承载,最后添加动画。
        for r in rects {
            let rectLayer = CAShapeLayer()
            
            rectLayer.fillColor = UIColor.orange.cgColor
            rectLayer.strokeColor = nil
            rectLayer.path = UIBezierPath(rect: CGRect(origin: CGPoint.zero, size: r.size)).cgPath
            rectLayer.frame = r
            
            var startTransform = CATransform3DIdentity
            startTransform.m34 = 1.0 / -20  // 透视
            startTransform = CATransform3DRotate(startTransform, CGFloat(Double.pi)*0.5, 0, 1, 0)  // 沿 y 轴旋转 π/2 圈,待会再动画转回来

            // transform 动画
            let transAnim = CABasicAnimation(keyPath: "transform")
            transAnim.duration = drand48() * 2  // 随机一个持续时间
            transAnim.fromValue = NSValue(caTransform3D: startTransform)
            transAnim.toValue = NSValue(caTransform3D: CATransform3DIdentity)
            transAnim.repeatCount = 5
            rectLayer.add(transAnim, forKey: "transAnim")

            // 透明度动画
            let alphaAnim = CABasicAnimation(keyPath: "opacity")
            alphaAnim.duration = transAnim.duration
            alphaAnim.fromValue = 0
            alphaAnim.toValue = 1
            alphaAnim.repeatCount = 5
            rectLayer.add(alphaAnim, forKey: "alphaAnim")
            
            layer.addSublayer(rectLayer)
        }
    }
}

Demo下载地址

上一篇下一篇

猜你喜欢

热点阅读