iOS 贝塞尔曲线路径动画 SVG快速实现(Swift版)
2019-02-25 本文已影响25人
老坛泡菜
本文将简单实现iOS快速路径绘制动画。
什么核心动画(Core Animation)、CAShapeLayer、UIBeizerPath等这些,这篇文章就不多说了。
反正看完这篇文章可以实现以下两种效果,虽然这两种效果很弱智,但高楼从地起嘛,懂了原理可以实现越来越复杂的动画组。Demo在最后可以下载,代码很烂,大神轻喷。
注:二维码效果来源:文章,因为原文里的部分功能无法重现,故在此重新写了个Demo。
案例一:快速实现路径动画
-
步骤:
1.方案有很多,可以通过Sketch软件,先绘制出想要的图,然后导出SVG,再用PaintCode软件将SVG文件转换成所需的代码。这里我直接通过PaintCode绘制路径图,导出代码啦。
2.打开PaintCode,使用钢笔工具,随便画一条线,在软件的下方会直接生成贝塞尔曲线的代码(PS:也可以根据自己需要更改所使用的编程语言)。
3.复制生成的代码,去Xcode创建一个UIView。
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)
}
}
案例二:快速实现二维码动画
- 步骤:
1.首先找一个可以生成二维码并能导出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)
}
}
}