• 读取svg图片为UIBezierPath,开心做动画


    动画预览

    先扯淡

    最近手痒又想整点动画玩玩,但是想了几个主意发现稍微复杂一点的手写都一定会累爆。这篇文章记录一下今天折腾的一个方案。说来简单,就是用矢量设计工具舒舒服服的做好设计,然后输出成 svg 格式,再用 NSXMLParser 去读出来,转换成 UIBezierPath ,然后就天高任鸟飞~

    清晰起见,这里不使用各种库,由上面的二维码动画为例,只转换最简单的矩形。需要更多高能操作的,出门右转 SVGKit

    开工

    筹备材料先,首先找个能提供 svg 格式下载的二维码生成网站,比如 这个 。拿到 svg 文件后用文本编辑器打开可以看到其实是一个描述矢量图形的 xml ,而且里面几百个矩形。。。如果你用的生成网站跟我一样,还会有一个白色的背景矩形,待会儿我们会把它排除掉。

    准备工作就到这了,接下来我们会用 NSXMLParser 来解析这个二维码。

    新建一个 Single View Application ,把二维码拖进项目里去,在 ViewController 里添加一个 UIView 作为二维码的容器:

    class ViewController: UIViewController {
      let qrView = UIView()
      override func viewDidLoad() {
        super.viewDidLoad()
        qrView.center = view.center
        view.addSubview(qrView)
      }
      ...
    }
    
    

    初始化一个 NSXMLParser 去解析 svg ,同时让 ViewController 实现 NSXMLParserDelegateparser(_:didStartElement:namespaceURI:qualifiedName:attributes:)parserDidEndDocument(_:) 两个方法用于处理解析结果:

    class ViewController: UIViewController, NSXMLParserDelegate {
      ...
      override func viewDidLoad() {
        ...
        let qrPath = NSBundle.mainBundle().pathForResource("zcfan_qrcode", ofType: "svg")!
        let qrData = NSData(contentsOfFile: qrPath)
        let xmlParser = NSXMLParser(data: qrData)
        xmlParser.delegate = self
        xmlParser.parse()
      }
      func parser(parser: NSXMLParser!, didStartElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!, attributes attributeDict: [NSObject : AnyObject]!) {
        // 每当解析到一个新标签,这里就会被调用
      }
      func parserDidEndDocument(parser: NSXMLParser!) {
        // 整个 svg 文件解析完毕后,这里就会被调用
      }
      ...
    }
    
    

    接下来我们会在 parser(_:didStartElement:namespaceURI:qualifiedName:attributes:) 中把遇到的每一个形如:

    <rect ... x="0" y="0" width="12" height="12" fill="black"/>
    

    的标签转换成 CGRect 保存在数组中,并在 parserDidEndDocument(_:) 中把他们转换为 CAShapeLayer 并添加动画。

    先来看看 parser(_:didStartElement:namespaceURI:qualifiedName:attributes:) 的内容:

    ...
    var rects = [CGRect]()  // 用于存储二维码
    func parser(parser: NSXMLParser!, didStartElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!, attributes attributeDict: [NSObject : AnyObject]!) {
      // 只转换 “黑色” 的二维码 “方块”
      if elementName == "rect" && (attributeDict["fill"] as String) == "black" {
        let x = (attributeDict["x"] as NSString).doubleValue
        let y = (attributeDict["y"] as NSString).doubleValue
        let w = (attributeDict["width"] as NSString).doubleValue
        let h = (attributeDict["height"] as NSString).doubleValue
        let rect = CGRect(x: x, y: y,  w, height: h)
        rects.append(rect)
      // 设置 qrView 的尺寸为 svg 图像的大小
      } else if elementName == "svg" {
        let w = (attributeDict["width"] as NSString).doubleValue
        let h = (attributeDict["height"] as NSString).doubleValue
        qrView.bounds = CGRect(x: 0, y: 0,  w, height: h)
      }
    }
    ...
    
    

    接下来是 parserDidEndDocument(_:) ,在这里我们要把二维码显示出来:

    ...
    func parserDidEndDocument(parser: NSXMLParser!) {
        for r in rects {
      let rectLayer = CAShapeLayer()
      rectLayer.fillColor = UIColor.darkGrayColor().CGColor
      rectLayer.strokeColor = nil
      rectLayer.path = UIBezierPath(rect: CGRect(origin: CGPointZero, size: r.size)).CGPath  // #1
      rectLayer.frame = r  // #2
      qrView.layer.addSublayer(rectLayer)
        }
    }
    ...
    
    

    #1、 #2 : 看着有点晕?代码不直观的话不妨稍微把玩一下,原因很简单,但要用语言解释我的舌头可能会打结。。。

    至此,运行项目应该就能在屏幕上看到一个大二维码了!

    加特技! Duang~

    回到上面的 parserDidEndDocument(_:) 方法,然后把它改到面目全非!Duang~

    func parserDidEndDocument(parser: NSXMLParser!) {
        qrView.layer.shadowColor = UIColor.grayColor().CGColor
        qrView.layer.shadowRadius = 4
        qrView.layer.shadowOpacity = 1
        qrView.layer.shadowOffset = CGSizeZero
        for r in rects {
      let rectLayer = CAShapeLayer()
      rectLayer.fillColor = UIColor.darkGrayColor().CGColor
      rectLayer.strokeColor = nil
      rectLayer.path = UIBezierPath(rect: CGRect(origin: CGPointZero, size: r.size)).CGPath
      rectLayer.frame = r
      var startTransform = CATransform3DIdentity
      startTransform.m34 = 1.0 / -20  // 透视
      startTransform = CATransform3DRotate(startTransform, CGFloat(M_PI)*0.5, 0, 1, 0)  // 沿 y 轴旋转 π/2 圈,待会再动画转回来
      // transform 动画
      let transAnim = CABasicAnimation(keyPath: "transform")
      transAnim.duration = drand48() * 4  // 随机一个持续时间
      transAnim.fromValue = NSValue(CATransform3D: startTransform)
      transAnim.toValue = NSValue(CATransform3D: CATransform3DIdentity)
      rectLayer.addAnimation(transAnim, forKey: "transAnim")
      // 透明度动画
      let alphaAnim = CABasicAnimation(keyPath: "opacity")
      alphaAnim.duration = transAnim.duration
      alphaAnim.fromValue = 0
      alphaAnim.toValue = 1
      rectLayer.addAnimation(alphaAnim, forKey: "alphaAnim")
      qrView.layer.addSublayer(rectLayer)
        }
    }
    
    

    完工

    没眼看,不录gif了。。。心塞。。。

    继续加特技

    手贱没忍住。。。二维码真是玩不坏。。。

  • 相关阅读:
    python 代码规范
    Helm 入门指南
    思路和决断
    awk替换第几行第几列的值
    一个awk命令的demo
    装饰模式
    Java多线程Thread.yield(),thread.join(), Thread.sleep(200),Object.wait(),Object.notify(),Object.notifyAll()的区别
    类继承时,构造函数和析构函数的调用次序
    C++中delete和 delete[]的区别
    回溯
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/5959822.html
Copyright © 2020-2023  润新知