动画示例(九) —— 一种复杂加载动画的实现 (一)
版本记录
版本号 | 时间 |
---|---|
V1.0 | 2018.08.25 |
前言
如果你细看了我前面写的有关动画的部分,就知道前面介绍了
CoreAnimation
、序列帧以及LOTAnimation
等很多动画方式,接下来几篇我们就以动画示例为线索,进行动画的讲解。相关代码已经上传至GitHub - 刀客传奇。感兴趣的可以看我写的前面几篇。
1. 动画示例(一) —— 一种外扩的简单动画
2. 动画示例(二) —— 一种抖动的简单动画
3. 动画示例(三) —— 仿头条一种LOTAnimation动画
4. 动画示例(四) —— QuartzCore之CAEmitterLayer下雪❄️动画
5. 动画示例(五) —— QuartzCore之CAEmitterLayer烟花动画
6. 动画示例(六) —— QuartzCore之CAEmitterLayer、CAReplicatorLayer和CAGradientLayer简单动画
7. 动画示例(七) —— 基于CAShapeLayer图像加载过程的简单动画(一)
8. 动画示例(八) —— UIViewController间转场动画的实现 (一)
开始
在App中,没有比在应用程序的加载屏幕上开始让用户惊叹的好地方了,您可以在其中添加令人愉快的动画。
在本文中,您将学习如何制作这样的动画。 您将学习如何逐个创建这个复杂的动画。
下载下来这个工程,打开HolderView.swift
,在这个UIView子类中,您将添加以下子图层(在Layers
组中找到)并为其设置动画,如上面的动画所示:
- OvalLayer.swift:这是第一个layer,它从零大小扩展,然后在短时间内摆动。
-
TriangleLayer.swift:当
OvalLayer
摇摆时出现下一层。当此视图旋转时,OvalLayer
收缩回零大小,只留下TriangleLayer
可见。 -
RectangleLayer.swift:此图层用作
TriangleLayer
的各种视觉容器。 -
ArcLayer.swift:此图层使用动画效果填充
RectangleLayer
,该动画效果与填充水的玻璃非常相似。
打开OvalLayer.swift
,项目已经包含初始化此图层的代码以及您将在动画中使用的所有Bezier
路径。你会看到expand()
,wobble()
和contract()
都是空的,在学习本教程时,您将填充这些方法。所有其他* Layer文件的结构都类似。
最后,打开ViewController.swift
并查看addHolderView()
,此方法将HolderView
的实例作为子视图添加到视图控制器视图的中心。 此视图将包含所有动画。 视图控制器只需要将它放在屏幕上,视图将处理实际的动画代码。
animateLabel()
函数是HolderView
类提供的委托回调,您将在完成动画序列时填写该回调。 addButton()
只是向视图添加一个按钮,以便您可以点击并重新启动动画。
Build并运行您的应用程序,你应该看到一个空的白色屏幕。
在本教程结束时,您的应用将如下所示:
Adding The Oval - 添加椭圆
动画以红色椭圆形开始,从屏幕中心扩展到视图,然后稍微摆动。
打开HolderView.swift
并在HolderView
类的顶部附近声明以下常量:
let ovalLayer = OvalLayer()
现在将以下函数添加到类的底部:
func addOval() {
layer.addSublayer(ovalLayer)
ovalLayer.expand()
}
这首先将您在上面创建的OvalLayer
实例作为子图层添加到视图的图层,然后调用expand()
,这是您需要填写的存根函数之一。
转到OvalLayer.swift
并将以下代码添加到expand()
:
func expand() {
var expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
expandAnimation.fromValue = ovalPathSmall.CGPath
expandAnimation.toValue = ovalPathLarge.CGPath
expandAnimation.duration = animationDuration
expandAnimation.fillMode = kCAFillModeForwards
expandAnimation.removedOnCompletion = false
addAnimation(expandAnimation, forKey: nil)
}
此函数创建一个CABasicAnimation
实例,用于将椭圆的路径从ovalPathSmall
更改为ovalPathLarge
。 入门项目为您提供这两种Bezier路径。 将removedOnCompletion
设置为false并将动画上的fillMode
设置为KCAFillModeForwards
可让椭圆在动画结束后保留其新路径。
最后,打开ViewController.swift
并将以下行添加到view.addSubview(holderView)
下面的addHolderView()
:
holderView.addOval()
这会调用addOval
,以便在将动画添加到视图控制器的视图后启动动画。
构建并运行您的应用程序,你的动画现在应该是这样的:
Wobbling The Oval - 摆动椭圆形
随着你的椭圆形现在扩展到view,下一步是在它的步骤中进行一些反弹并使其摆动。
打开HolderView.swift
并将以下函数添加到类的底部:
func wobbleOval() {
ovalLayer.wobble()
}
这在OvalLayer
中调用了stubbed-out
方法wobble()
。
现在打开OvalLayer.swift
并将以下代码添加到wobble()
:
func wobble() {
// 1
var wobbleAnimation1: CABasicAnimation = CABasicAnimation(keyPath: "path")
wobbleAnimation1.fromValue = ovalPathLarge.CGPath
wobbleAnimation1.toValue = ovalPathSquishVertical.CGPath
wobbleAnimation1.beginTime = 0.0
wobbleAnimation1.duration = animationDuration
// 2
var wobbleAnimation2: CABasicAnimation = CABasicAnimation(keyPath: "path")
wobbleAnimation2.fromValue = ovalPathSquishVertical.CGPath
wobbleAnimation2.toValue = ovalPathSquishHorizontal.CGPath
wobbleAnimation2.beginTime = wobbleAnimation1.beginTime + wobbleAnimation1.duration
wobbleAnimation2.duration = animationDuration
// 3
var wobbleAnimation3: CABasicAnimation = CABasicAnimation(keyPath: "path")
wobbleAnimation3.fromValue = ovalPathSquishHorizontal.CGPath
wobbleAnimation3.toValue = ovalPathSquishVertical.CGPath
wobbleAnimation3.beginTime = wobbleAnimation2.beginTime + wobbleAnimation2.duration
wobbleAnimation3.duration = animationDuration
// 4
var wobbleAnimation4: CABasicAnimation = CABasicAnimation(keyPath: "path")
wobbleAnimation4.fromValue = ovalPathSquishVertical.CGPath
wobbleAnimation4.toValue = ovalPathLarge.CGPath
wobbleAnimation4.beginTime = wobbleAnimation3.beginTime + wobbleAnimation3.duration
wobbleAnimation4.duration = animationDuration
// 5
var wobbleAnimationGroup: CAAnimationGroup = CAAnimationGroup()
wobbleAnimationGroup.animations = [wobbleAnimation1, wobbleAnimation2, wobbleAnimation3,
wobbleAnimation4]
wobbleAnimationGroup.duration = wobbleAnimation4.beginTime + wobbleAnimation4.duration
wobbleAnimationGroup.repeatCount = 2
addAnimation(wobbleAnimationGroup, forKey: nil)
}
这是很多代码,但它很好地分解了。 这是发生了什么:
- 1)从大路径动画到垂直压扁。
- 2)从垂直挤压变为水平和垂直挤压。
- 3)交换回垂直挤压。
- 4)完成动画,回到大路径。
- 5)将所有动画组合到
CAAnimationGroup
中,并将此组动画添加到OvalLayout
。
每个后续动画的beginTime
是前一个动画的beginTime
与其duration
的总和。 您重复动画组两次,使摆动略微拉长。
即使您现在拥有生成摆动动画所需的所有代码,但您还没有调用新动画。
返回HolderView.swift
并将以下行添加到addOval()
的末尾:
NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: "wobbleOval",
userInfo: nil, repeats: false)
在这里,您可以创建一个在OvalLayer
完成扩展后立即调用wobbleOval()
的计时器。
构建并运行您的应用程序,看看你的新动画:
Beginning The Morph - 开始变形
是时候变得有点花哨了!你要把椭圆变成三角形。 对于用户来说,这种转变应该看起来完全无缝。 您将使用两种不同形状的相同颜色来完成这项工作。
打开HolderView.swift
并将以下代码添加到HolderView
类的顶部,就在您之前添加的ovalLayer
属性的下方:
let triangleLayer = TriangleLayer()
这声明了TriangleLayer
的常量实例,就像你为OvalLayer
所做的那样。
现在,让wobbleOval()
看起来像这样:
func wobbleOval() {
// 1
layer.addSublayer(triangleLayer) // Add this line
ovalLayer.wobble()
// 2
// Add the code below
NSTimer.scheduledTimerWithTimeInterval(0.9, target: self,
selector: "drawAnimatedTriangle", userInfo: nil,
repeats: false)
}
上面的代码执行以下操作:
- 1)此行将您之前初始化的
TriangleLayer
实例添加为HolderView
图层的子图层。 - 2)由于您知道摆动动画在1.8的总持续时间内运行两次,因此中途点将是开始变形过程的好地方。 因此,您添加一个计时器,在延迟0.9后添加
drawAnimatedTriangle()
。
注意:为动画找到合适的持续时间或延迟需要一些试验和错误,并且可能意味着好动画和梦幻动画之间的区别。 我鼓励你修改你的动画,让它们看起来很完美。 这可能需要一些时间,但值得!
接下来,将以下函数添加到类的底部:
func drawAnimatedTriangle() {
triangleLayer.animate()
}
从刚添加到wobbleOval()
的计时器调用此方法。 它调用triangleLayer
中的(当前存根)方法,该方法使三角形动画化。
现在打开TriangleLayer.swift
并将以下代码添加到animate()
:
func animate() {
var triangleAnimationLeft: CABasicAnimation = CABasicAnimation(keyPath: "path")
triangleAnimationLeft.fromValue = trianglePathSmall.CGPath
triangleAnimationLeft.toValue = trianglePathLeftExtension.CGPath
triangleAnimationLeft.beginTime = 0.0
triangleAnimationLeft.duration = 0.3
var triangleAnimationRight: CABasicAnimation = CABasicAnimation(keyPath: "path")
triangleAnimationRight.fromValue = trianglePathLeftExtension.CGPath
triangleAnimationRight.toValue = trianglePathRightExtension.CGPath
triangleAnimationRight.beginTime = triangleAnimationLeft.beginTime + triangleAnimationLeft.duration
triangleAnimationRight.duration = 0.25
var triangleAnimationTop: CABasicAnimation = CABasicAnimation(keyPath: "path")
triangleAnimationTop.fromValue = trianglePathRightExtension.CGPath
triangleAnimationTop.toValue = trianglePathTopExtension.CGPath
triangleAnimationTop.beginTime = triangleAnimationRight.beginTime + triangleAnimationRight.duration
triangleAnimationTop.duration = 0.20
var triangleAnimationGroup: CAAnimationGroup = CAAnimationGroup()
triangleAnimationGroup.animations = [triangleAnimationLeft, triangleAnimationRight,
triangleAnimationTop]
triangleAnimationGroup.duration = triangleAnimationTop.beginTime + triangleAnimationTop.duration
triangleAnimationGroup.fillMode = kCAFillModeForwards
triangleAnimationGroup.removedOnCompletion = false
addAnimation(triangleAnimationGroup, forKey: nil)
}
当OvalLayer
摆动时,此代码会激活TriangleLayer
的角落以逐个弹出,作为入门项目的一部分,已经为每个角定义了Bezier
路径。 左边是第一个,然后是右边,然后是顶部。 您可以通过创建添加到CAAnimationGroup
的基于路径的CABasicAnimation
的三个实例来执行此操作,然后再添加到TriangleLayer
。
构建并运行应用程序以查看动画的当前状态,当椭圆形摆动时,三角形的每个角都开始出现,直到所有三个角都可见,如下所示:
Completing The Morph - 完成变形
要完成变形过程,您可以在收缩OvalLayer
时将HolderView
旋转360度,只留下TriangleLayer
。
打开HolderView.swift
将以下代码添加到drawAnimatedTriangle()
的末尾:
NSTimer.scheduledTimerWithTimeInterval(0.9, target: self, selector: "spinAndTransform",
userInfo: nil, repeats: false)
这将设置一个在三角形动画结束后触发的计时器。 0.9秒的时间再次通过反复试验来确定。
现在将以下函数添加到类的底部:
func spinAndTransform(){
// 1
layer.anchorPoint = CGPointMake(0.5,0.6)
// 2
var rotationAnimation:CABasicAnimation = CABasicAnimation(keyPath:“transform.rotation.z”)
rotationAnimation.toValue = CGFloat(M_PI * 2.0)
rotationAnimation.duration = 0.45
rotationAnimation.removedOnCompletion = true
layer.addAnimation(rotationAnimation,forKey:nil)
// 3
ovalLayer.contract()
}
在您添加此代码之前创建的计时器会在椭圆停止摆动并且出现三角形的所有角时调用此函数。 这里更详细地看一下这个函数:
- 1)将图层的锚点更新为略低于视图的中心。 这提供了更自然的旋转。 这是因为椭圆和三角实际上是垂直偏离视图的中心。 因此,如果视图围绕其中心旋转,则椭圆和三角形似乎垂直移动。
- 2)应用
CABasicAnimation
将图层旋转360度或2 * Pi
弧度。 旋转围绕z轴,z轴是进出屏幕的轴,垂直于屏幕表面。 - 3)在
OvalLayer
上调用contract()
来执行减小椭圆大小的动画,直到它不再可见。
现在打开OvalLayer.swift
并将以下代码添加到contract()
:
func contract() {
var contractAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
contractAnimation.fromValue = ovalPathLarge.CGPath
contractAnimation.toValue = ovalPathSmall.CGPath
contractAnimation.duration = animationDuration
contractAnimation.fillMode = kCAFillModeForwards
contractAnimation.removedOnCompletion = false
addAnimation(contractAnimation, forKey: nil)
}
这通过应用CABasicAnimation
将OvalLayer
设置回其初始路径ovalPathSmall
。 这与expand()
完全相反,您在动画开始时调用它。
构建并运行您的应用程序,动画完成后,三角形是唯一应留在屏幕上的东西:
Drawing The Container - 绘制容器
在下一部分中,您将为矩形容器的绘图设置动画以创建容器。 为此,您将使用RectangleLayer
的stroke
属性。 你会做两次,使用红色和蓝色作为stroke
颜色。
打开HolderView.swift
并在之前添加的triangleLayer
属性下面声明两个RectangularLayer
常量:
let redRectangleLayer = RectangleLayer()
let blueRectangleLayer = RectangleLayer()
接下来将以下代码添加到spinAndTransform()
的末尾:
NSTimer.scheduledTimerWithTimeInterval(0.45, target: self,
selector: "drawRedAnimatedRectangle",
userInfo: nil, repeats: false)
NSTimer.scheduledTimerWithTimeInterval(0.65, target: self,
selector: "drawBlueAnimatedRectangle",
userInfo: nil, repeats: false)
在这里,您将创建两个分别调用drawRedAnimatedRectangle()
和drawBlueAnimatedRectangle()
的计时器。 首先在旋转动画完成后立即绘制红色矩形。 当红色矩形的笔划接近完成时,蓝色矩形的笔划开始。
将以下两个函数添加到类的底部:
func drawRedAnimatedRectangle() {
layer.addSublayer(redRectangleLayer)
redRectangleLayer.animateStrokeWithColor(Colors.red)
}
func drawBlueAnimatedRectangle() {
layer.addSublayer(blueRectangleLayer)
blueRectangleLayer.animateStrokeWithColor(Colors.blue)
}
将RectangleLayer
作为子图层添加到HolderView
后,可以调用animateStrokeWithColor(color :)
并传入适当的颜色以设置边框绘制的动画。
现在打开RectangleLayer.swift
并填充animateStrokeWithColor(color :)
,如下所示:
func animateStrokeWithColor(color: UIColor) {
strokeColor = color.CGColor
var strokeAnimation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
strokeAnimation.fromValue = 0.0
strokeAnimation.toValue = 1.0
strokeAnimation.duration = 0.4
addAnimation(strokeAnimation, forKey: nil)
}
这会通过向其添加CABasicAnimation
来绘制RectangleLayer
周围的stroke
。CAShapeLayer
的strokeEnd
键指示停止描边的路径周围。 通过将此属性设置为0到1的动画,可以创建从开始到结束绘制路径的错觉。 动画从1到0会产生整个路径被擦掉的错觉。
构建并运行您的应用程序,以查看两个stroke
在构建容器时的外观:
Filling In The Container - 填充容器
现在您的容器已经好了,动画的下一个阶段就是填充它。 你正在寻找的效果是水填满玻璃。 这是一个很棒的视觉效果。
打开HolderView.swift
并在两个RectangleLayer
属性的正下方添加以下常量:
let arcLayer = ArcLayer()
现在将以下代码添加到drawBlueAnimatedRectangle()
的末尾:
NSTimer.scheduledTimerWithTimeInterval(0.40, target: self, selector: "drawArc",
userInfo: nil, repeats: false)
一旦蓝色RectangleLayer
完成绘制,这将创建一个调用drawArc()
的计时器。
将以下函数添加到类的末尾:
func drawArc() {
layer.addSublayer(arcLayer)
arcLayer.animate()
}
这会在您在填充中设置动画之前将上面创建的ArcLayer
实例添加到HolderView
的图层。
打开ArcLayer.swift
并将以下代码添加到animate()
:
func animate() {
var arcAnimationPre: CABasicAnimation = CABasicAnimation(keyPath: "path")
arcAnimationPre.fromValue = arcPathPre.CGPath
arcAnimationPre.toValue = arcPathStarting.CGPath
arcAnimationPre.beginTime = 0.0
arcAnimationPre.duration = animationDuration
var arcAnimationLow: CABasicAnimation = CABasicAnimation(keyPath: "path")
arcAnimationLow.fromValue = arcPathStarting.CGPath
arcAnimationLow.toValue = arcPathLow.CGPath
arcAnimationLow.beginTime = arcAnimationPre.beginTime + arcAnimationPre.duration
arcAnimationLow.duration = animationDuration
var arcAnimationMid: CABasicAnimation = CABasicAnimation(keyPath: "path")
arcAnimationMid.fromValue = arcPathLow.CGPath
arcAnimationMid.toValue = arcPathMid.CGPath
arcAnimationMid.beginTime = arcAnimationLow.beginTime + arcAnimationLow.duration
arcAnimationMid.duration = animationDuration
var arcAnimationHigh: CABasicAnimation = CABasicAnimation(keyPath: "path")
arcAnimationHigh.fromValue = arcPathMid.CGPath
arcAnimationHigh.toValue = arcPathHigh.CGPath
arcAnimationHigh.beginTime = arcAnimationMid.beginTime + arcAnimationMid.duration
arcAnimationHigh.duration = animationDuration
var arcAnimationComplete: CABasicAnimation = CABasicAnimation(keyPath: "path")
arcAnimationComplete.fromValue = arcPathHigh.CGPath
arcAnimationComplete.toValue = arcPathComplete.CGPath
arcAnimationComplete.beginTime = arcAnimationHigh.beginTime + arcAnimationHigh.duration
arcAnimationComplete.duration = animationDuration
var arcAnimationGroup: CAAnimationGroup = CAAnimationGroup()
arcAnimationGroup.animations = [arcAnimationPre, arcAnimationLow, arcAnimationMid,
arcAnimationHigh, arcAnimationComplete]
arcAnimationGroup.duration = arcAnimationComplete.beginTime + arcAnimationComplete.duration
arcAnimationGroup.fillMode = kCAFillModeForwards
arcAnimationGroup.removedOnCompletion = false
addAnimation(arcAnimationGroup, forKey: nil)
}
此动画与之前的摆动动画非常相似; 您创建一个CAAnimationGroup
,其中包含基于路径的CABasicAnimation
的五个实例。 每条路径都有一个略微不同的弧,随着高度的增加而成为入门项目的一部分。 最后,将CAAnimationGroup
应用于图层并指示在完成时不将其删除,以便在动画完成时保持其状态。
构建并运行您的应用程序:
Completing The Animation - 完成动画
剩下要做的就是展开蓝色HolderView
以填充整个屏幕,并在视图中添加UILabel
作为logo。
打开HolderView.swift
并将以下代码添加到drawArc()
的末尾:
NSTimer.scheduledTimerWithTimeInterval(0.90, target: self, selector: "expandView",
userInfo: nil, repeats: false)
这将创建一个计时器,在ArcLayer
填满容器后调用expandView()
。
现在,将以下函数添加到同一个类的底部:
func expandView() {
// 1
backgroundColor = Colors.blue
// 2
frame = CGRectMake(frame.origin.x - blueRectangleLayer.lineWidth,
frame.origin.y - blueRectangleLayer.lineWidth,
frame.size.width + blueRectangleLayer.lineWidth * 2,
frame.size.height + blueRectangleLayer.lineWidth * 2)
// 3
layer.sublayers = nil
// 4
UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut,
animations: {
self.frame = self.parentFrame
}, completion: { finished in
self.addLabel()
})
}
这是该方法的作用:
- 1)
holder
视图的背景设置为蓝色,以匹配您填充矩形的颜色。 - 2)frame将展开以说明您之前添加的
RectangleLayer
的stroke
宽度。 - 3)所有子图层都被删除。 现在没有椭圆形,没有三角形,也没有矩形层。
- 4)添加动画以展开
HolderView
以填充屏幕。 完成动画后,调用addLabel()
。
将以下函数添加到类的底部:
func addLabel() {
delegate?.animateLabel()
}
这只是调用视图的代理函数来为Label设置动画。
现在打开ViewController.swift
并将以下代码添加到animateLabel()
:
func animateLabel() {
// 1
holderView.removeFromSuperview()
view.backgroundColor = Colors.blue
// 2
var label: UILabel = UILabel(frame: view.frame)
label.textColor = Colors.white
label.font = UIFont(name: "HelveticaNeue-Thin", size: 170.0)
label.textAlignment = NSTextAlignment.Center
label.text = "S"
label.transform = CGAffineTransformScale(label.transform, 0.25, 0.25)
view.addSubview(label)
// 3
UIView.animateWithDuration(0.4, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.1, options: UIViewAnimationOptions.CurveEaseInOut,
animations: ({
label.transform = CGAffineTransformScale(label.transform, 4.0, 4.0)
}), completion: { finished in
self.addButton()
})
}
下面进行详细的说明和分解:
- 1)从视图中删除
HolderView
并将视图的背景颜色设置为蓝色。 - 2)创建一个文本为“S”的
UILabel
来表示logo,并将其添加到视图中。 - 3)将弹簧动画应用于label以进行缩放。动画完成后,调用
addButton()
以向视图添加按钮,按下该按钮时会重复动画。
构建并运行应用程序:
源码
1. ViewController.swift
import UIKit
class ViewController: UIViewController, HolderViewDelegate {
var holderView = HolderView(frame: CGRectZero)
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
addHolderView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func addHolderView() {
let boxSize: CGFloat = 100.0
holderView.frame = CGRect(x: view.bounds.width / 2 - boxSize / 2,
y: view.bounds.height / 2 - boxSize / 2,
width: boxSize,
height: boxSize)
holderView.parentFrame = view.frame
holderView.delegate = self
view.addSubview(holderView)
holderView.addOval()
}
func animateLabel() {
// 1
holderView.removeFromSuperview()
view.backgroundColor = Colors.blue
// 2
var label: UILabel = UILabel(frame: view.frame)
label.textColor = Colors.white
label.font = UIFont(name: "HelveticaNeue-Thin", size: 170.0)
label.textAlignment = NSTextAlignment.Center
label.text = "S"
label.transform = CGAffineTransformScale(label.transform, 0.25, 0.25)
view.addSubview(label)
// 3
UIView.animateWithDuration(0.4, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.1, options: UIViewAnimationOptions.CurveEaseInOut,
animations: ({
label.transform = CGAffineTransformScale(label.transform, 4.0, 4.0)
}), completion: { finished in
self.addButton()
})
}
func addButton() {
let button = UIButton()
button.frame = CGRectMake(0.0, 0.0, view.bounds.width, view.bounds.height)
button.addTarget(self, action: "buttonPressed:", forControlEvents: .TouchUpInside)
view.addSubview(button)
}
func buttonPressed(sender: UIButton!) {
view.backgroundColor = Colors.white
view.subviews.map({ $0.removeFromSuperview() })
holderView = HolderView(frame: CGRectZero)
addHolderView()
}
}
2. OvalLayer.swift
import UIKit
class OvalLayer: CAShapeLayer {
let animationDuration: CFTimeInterval = 0.3
override init!() {
super.init()
fillColor = Colors.red.CGColor
path = ovalPathSmall.CGPath
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var ovalPathSmall: UIBezierPath {
return UIBezierPath(ovalInRect: CGRect(x: 50.0, y: 50.0, width: 0.0, height: 0.0))
}
var ovalPathLarge: UIBezierPath {
return UIBezierPath(ovalInRect: CGRect(x: 2.5, y: 17.5, width: 95.0, height: 95.0))
}
var ovalPathSquishVertical: UIBezierPath {
return UIBezierPath(ovalInRect: CGRect(x: 2.5, y: 20.0, width: 95.0, height: 90.0))
}
var ovalPathSquishHorizontal: UIBezierPath {
return UIBezierPath(ovalInRect: CGRect(x: 5.0, y: 20.0, width: 90.0, height: 90.0))
}
func expand() {
var expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
expandAnimation.fromValue = ovalPathSmall.CGPath
expandAnimation.toValue = ovalPathLarge.CGPath
expandAnimation.duration = animationDuration
expandAnimation.fillMode = kCAFillModeForwards
expandAnimation.removedOnCompletion = false
addAnimation(expandAnimation, forKey: nil)
}
func wobble() {
// 1
var wobbleAnimation1: CABasicAnimation = CABasicAnimation(keyPath: "path")
wobbleAnimation1.fromValue = ovalPathLarge.CGPath
wobbleAnimation1.toValue = ovalPathSquishVertical.CGPath
wobbleAnimation1.beginTime = 0.0
wobbleAnimation1.duration = animationDuration
// 2
var wobbleAnimation2: CABasicAnimation = CABasicAnimation(keyPath: "path")
wobbleAnimation2.fromValue = ovalPathSquishVertical.CGPath
wobbleAnimation2.toValue = ovalPathSquishHorizontal.CGPath
wobbleAnimation2.beginTime = wobbleAnimation1.beginTime + wobbleAnimation1.duration
wobbleAnimation2.duration = animationDuration
// 3
var wobbleAnimation3: CABasicAnimation = CABasicAnimation(keyPath: "path")
wobbleAnimation3.fromValue = ovalPathSquishHorizontal.CGPath
wobbleAnimation3.toValue = ovalPathSquishVertical.CGPath
wobbleAnimation3.beginTime = wobbleAnimation2.beginTime + wobbleAnimation2.duration
wobbleAnimation3.duration = animationDuration
// 4
var wobbleAnimation4: CABasicAnimation = CABasicAnimation(keyPath: "path")
wobbleAnimation4.fromValue = ovalPathSquishVertical.CGPath
wobbleAnimation4.toValue = ovalPathLarge.CGPath
wobbleAnimation4.beginTime = wobbleAnimation3.beginTime + wobbleAnimation3.duration
wobbleAnimation4.duration = animationDuration
// 5
var wobbleAnimationGroup: CAAnimationGroup = CAAnimationGroup()
wobbleAnimationGroup.animations = [wobbleAnimation1, wobbleAnimation2, wobbleAnimation3,
wobbleAnimation4]
wobbleAnimationGroup.duration = wobbleAnimation4.beginTime + wobbleAnimation4.duration
wobbleAnimationGroup.repeatCount = 2
addAnimation(wobbleAnimationGroup, forKey: nil)
}
func contract() {
var contractAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
contractAnimation.fromValue = ovalPathLarge.CGPath
contractAnimation.toValue = ovalPathSmall.CGPath
contractAnimation.duration = animationDuration
contractAnimation.fillMode = kCAFillModeForwards
contractAnimation.removedOnCompletion = false
addAnimation(contractAnimation, forKey: nil)
}
}
3. TriangleLayer.swift
import UIKit
class TriangleLayer: CAShapeLayer {
let innerPadding: CGFloat = 30.0
override init!() {
super.init()
fillColor = Colors.red.CGColor
strokeColor = Colors.red.CGColor
lineWidth = 7.0
lineCap = kCALineCapRound
lineJoin = kCALineJoinRound
path = trianglePathSmall.CGPath
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var trianglePathSmall: UIBezierPath {
var trianglePath = UIBezierPath()
trianglePath.moveToPoint(CGPoint(x: 5.0 + innerPadding, y: 95.0))
trianglePath.addLineToPoint(CGPoint(x: 50.0, y: 12.5 + innerPadding))
trianglePath.addLineToPoint(CGPoint(x: 95.0 - innerPadding, y: 95.0))
trianglePath.closePath()
return trianglePath
}
var trianglePathLeftExtension: UIBezierPath {
var trianglePath = UIBezierPath()
trianglePath.moveToPoint(CGPoint(x: 5.0, y: 95.0))
trianglePath.addLineToPoint(CGPoint(x: 50.0, y: 12.5 + innerPadding))
trianglePath.addLineToPoint(CGPoint(x: 95.0 - innerPadding, y: 95.0))
trianglePath.closePath()
return trianglePath
}
var trianglePathRightExtension: UIBezierPath {
var trianglePath = UIBezierPath()
trianglePath.moveToPoint(CGPoint(x: 5.0, y: 95.0))
trianglePath.addLineToPoint(CGPoint(x: 50.0, y: 12.5 + innerPadding))
trianglePath.addLineToPoint(CGPoint(x: 95.0, y: 95.0))
trianglePath.closePath()
return trianglePath
}
var trianglePathTopExtension: UIBezierPath {
var trianglePath = UIBezierPath()
trianglePath.moveToPoint(CGPoint(x: 5.0, y: 95.0))
trianglePath.addLineToPoint(CGPoint(x: 50.0, y: 12.5))
trianglePath.addLineToPoint(CGPoint(x: 95.0, y: 95.0))
trianglePath.closePath()
return trianglePath
}
func animate() {
var triangleAnimationLeft: CABasicAnimation = CABasicAnimation(keyPath: "path")
triangleAnimationLeft.fromValue = trianglePathSmall.CGPath
triangleAnimationLeft.toValue = trianglePathLeftExtension.CGPath
triangleAnimationLeft.beginTime = 0.0
triangleAnimationLeft.duration = 0.3
var triangleAnimationRight: CABasicAnimation = CABasicAnimation(keyPath: "path")
triangleAnimationRight.fromValue = trianglePathLeftExtension.CGPath
triangleAnimationRight.toValue = trianglePathRightExtension.CGPath
triangleAnimationRight.beginTime = triangleAnimationLeft.beginTime + triangleAnimationLeft.duration
triangleAnimationRight.duration = 0.25
var triangleAnimationTop: CABasicAnimation = CABasicAnimation(keyPath: "path")
triangleAnimationTop.fromValue = trianglePathRightExtension.CGPath
triangleAnimationTop.toValue = trianglePathTopExtension.CGPath
triangleAnimationTop.beginTime = triangleAnimationRight.beginTime + triangleAnimationRight.duration
triangleAnimationTop.duration = 0.20
var triangleAnimationGroup: CAAnimationGroup = CAAnimationGroup()
triangleAnimationGroup.animations = [triangleAnimationLeft, triangleAnimationRight,
triangleAnimationTop]
triangleAnimationGroup.duration = triangleAnimationTop.beginTime + triangleAnimationTop.duration
triangleAnimationGroup.fillMode = kCAFillModeForwards
triangleAnimationGroup.removedOnCompletion = false
addAnimation(triangleAnimationGroup, forKey: nil)
}
}
4. RectangleLayer.swift
import UIKit
class RectangleLayer: CAShapeLayer {
override init!() {
super.init()
fillColor = Colors.clear.CGColor
lineWidth = 5.0
path = rectanglePathFull.CGPath
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var rectanglePathFull: UIBezierPath {
var rectanglePath = UIBezierPath()
rectanglePath.moveToPoint(CGPoint(x: 0.0, y: 100.0))
rectanglePath.addLineToPoint(CGPoint(x: 0.0, y: -lineWidth))
rectanglePath.addLineToPoint(CGPoint(x: 100.0, y: -lineWidth))
rectanglePath.addLineToPoint(CGPoint(x: 100.0, y: 100.0))
rectanglePath.addLineToPoint(CGPoint(x: -lineWidth / 2, y: 100.0))
rectanglePath.closePath()
return rectanglePath
}
func animateStrokeWithColor(color: UIColor) {
strokeColor = color.CGColor
var strokeAnimation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
strokeAnimation.fromValue = 0.0
strokeAnimation.toValue = 1.0
strokeAnimation.duration = 0.4
addAnimation(strokeAnimation, forKey: nil)
}
}
5. ArcLayer.swift
import UIKit
class ArcLayer: CAShapeLayer {
let animationDuration: CFTimeInterval = 0.18
override init!() {
super.init()
fillColor = Colors.blue.CGColor
path = arcPathStarting.CGPath
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var arcPathPre: UIBezierPath {
var arcPath = UIBezierPath()
arcPath.moveToPoint(CGPoint(x: 0.0, y: 100.0))
arcPath.addLineToPoint(CGPoint(x: 0.0, y: 99.0))
arcPath.addLineToPoint(CGPoint(x: 100.0, y: 99.0))
arcPath.addLineToPoint(CGPoint(x: 100.0, y: 100.0))
arcPath.addLineToPoint(CGPoint(x: 0.0, y: 100.0))
arcPath.closePath()
return arcPath
}
var arcPathStarting: UIBezierPath {
var arcPath = UIBezierPath()
arcPath.moveToPoint(CGPoint(x: 0.0, y: 100.0))
arcPath.addLineToPoint(CGPoint(x: 0.0, y: 80.0))
arcPath.addCurveToPoint(CGPoint(x: 100.0, y: 80.0), controlPoint1: CGPoint(x: 30.0, y: 70.0), controlPoint2: CGPoint(x: 40.0, y: 90.0))
arcPath.addLineToPoint(CGPoint(x: 100.0, y: 100.0))
arcPath.addLineToPoint(CGPoint(x: 0.0, y: 100.0))
arcPath.closePath()
return arcPath
}
var arcPathLow: UIBezierPath {
var arcPath = UIBezierPath()
arcPath.moveToPoint(CGPoint(x: 0.0, y: 100.0))
arcPath.addLineToPoint(CGPoint(x: 0.0, y: 60.0))
arcPath.addCurveToPoint(CGPoint(x: 100.0, y: 60.0), controlPoint1: CGPoint(x: 30.0, y: 65.0), controlPoint2: CGPoint(x: 40.0, y: 50.0))
arcPath.addLineToPoint(CGPoint(x: 100.0, y: 100.0))
arcPath.addLineToPoint(CGPoint(x: 0.0, y: 100.0))
arcPath.closePath()
return arcPath
}
var arcPathMid: UIBezierPath {
var arcPath = UIBezierPath()
arcPath.moveToPoint(CGPoint(x: 0.0, y: 100.0))
arcPath.addLineToPoint(CGPoint(x: 0.0, y: 40.0))
arcPath.addCurveToPoint(CGPoint(x: 100.0, y: 40.0), controlPoint1: CGPoint(x: 30.0, y: 30.0), controlPoint2: CGPoint(x: 40.0, y: 50.0))
arcPath.addLineToPoint(CGPoint(x: 100.0, y: 100.0))
arcPath.addLineToPoint(CGPoint(x: 0.0, y: 100.0))
arcPath.closePath()
return arcPath
}
var arcPathHigh: UIBezierPath {
var arcPath = UIBezierPath()
arcPath.moveToPoint(CGPoint(x: 0.0, y: 100.0))
arcPath.addLineToPoint(CGPoint(x: 0.0, y: 20.0))
arcPath.addCurveToPoint(CGPoint(x: 100.0, y: 20.0), controlPoint1: CGPoint(x: 30.0, y: 25.0), controlPoint2: CGPoint(x: 40.0, y: 10.0))
arcPath.addLineToPoint(CGPoint(x: 100.0, y: 100.0))
arcPath.addLineToPoint(CGPoint(x: 0.0, y: 100.0))
arcPath.closePath()
return arcPath
}
var arcPathComplete: UIBezierPath {
var arcPath = UIBezierPath()
arcPath.moveToPoint(CGPoint(x: 0.0, y: 100.0))
arcPath.addLineToPoint(CGPoint(x: 0.0, y: -5.0))
arcPath.addLineToPoint(CGPoint(x: 100.0, y: -5.0))
arcPath.addLineToPoint(CGPoint(x: 100.0, y: 100.0))
arcPath.addLineToPoint(CGPoint(x: 0.0, y: 100.0))
arcPath.closePath()
return arcPath
}
func animate() {
var arcAnimationPre: CABasicAnimation = CABasicAnimation(keyPath: "path")
arcAnimationPre.fromValue = arcPathPre.CGPath
arcAnimationPre.toValue = arcPathStarting.CGPath
arcAnimationPre.beginTime = 0.0
arcAnimationPre.duration = animationDuration
var arcAnimationLow: CABasicAnimation = CABasicAnimation(keyPath: "path")
arcAnimationLow.fromValue = arcPathStarting.CGPath
arcAnimationLow.toValue = arcPathLow.CGPath
arcAnimationLow.beginTime = arcAnimationPre.beginTime + arcAnimationPre.duration
arcAnimationLow.duration = animationDuration
var arcAnimationMid: CABasicAnimation = CABasicAnimation(keyPath: "path")
arcAnimationMid.fromValue = arcPathLow.CGPath
arcAnimationMid.toValue = arcPathMid.CGPath
arcAnimationMid.beginTime = arcAnimationLow.beginTime + arcAnimationLow.duration
arcAnimationMid.duration = animationDuration
var arcAnimationHigh: CABasicAnimation = CABasicAnimation(keyPath: "path")
arcAnimationHigh.fromValue = arcPathMid.CGPath
arcAnimationHigh.toValue = arcPathHigh.CGPath
arcAnimationHigh.beginTime = arcAnimationMid.beginTime + arcAnimationMid.duration
arcAnimationHigh.duration = animationDuration
var arcAnimationComplete: CABasicAnimation = CABasicAnimation(keyPath: "path")
arcAnimationComplete.fromValue = arcPathHigh.CGPath
arcAnimationComplete.toValue = arcPathComplete.CGPath
arcAnimationComplete.beginTime = arcAnimationHigh.beginTime + arcAnimationHigh.duration
arcAnimationComplete.duration = animationDuration
var arcAnimationGroup: CAAnimationGroup = CAAnimationGroup()
arcAnimationGroup.animations = [arcAnimationPre, arcAnimationLow, arcAnimationMid,
arcAnimationHigh, arcAnimationComplete]
arcAnimationGroup.duration = arcAnimationComplete.beginTime + arcAnimationComplete.duration
arcAnimationGroup.fillMode = kCAFillModeForwards
arcAnimationGroup.removedOnCompletion = false
addAnimation(arcAnimationGroup, forKey: nil)
}
}
6. HolderView.swift
import UIKit
protocol HolderViewDelegate:class {
func animateLabel()
}
class HolderView: UIView {
let ovalLayer = OvalLayer()
let triangleLayer = TriangleLayer()
let redRectangleLayer = RectangleLayer()
let blueRectangleLayer = RectangleLayer()
let arcLayer = ArcLayer()
var parentFrame :CGRect = CGRectZero
weak var delegate:HolderViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = Colors.clear
}
required init(coder: NSCoder) {
super.init(coder: coder)
}
func addOval() {
layer.addSublayer(ovalLayer)
ovalLayer.expand()
NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: "wobbleOval",
userInfo: nil, repeats: false)
}
func wobbleOval() {
ovalLayer.wobble()
// 1
layer.addSublayer(triangleLayer) // Add this line
ovalLayer.wobble()
// 2
// Add the code below
NSTimer.scheduledTimerWithTimeInterval(0.9, target: self,
selector: "drawAnimatedTriangle", userInfo: nil,
repeats: false)
}
func drawAnimatedTriangle() {
triangleLayer.animate()
NSTimer.scheduledTimerWithTimeInterval(0.9, target: self, selector: "spinAndTransform",
userInfo: nil, repeats: false)
}
func spinAndTransform() {
// 1
layer.anchorPoint = CGPointMake(0.5, 0.6)
// 2
var rotationAnimation: CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.toValue = CGFloat(M_PI * 2.0)
rotationAnimation.duration = 0.45
rotationAnimation.removedOnCompletion = true
layer.addAnimation(rotationAnimation, forKey: nil)
// 3
ovalLayer.contract()
NSTimer.scheduledTimerWithTimeInterval(0.45, target: self,
selector: "drawRedAnimatedRectangle",
userInfo: nil, repeats: false)
NSTimer.scheduledTimerWithTimeInterval(0.65, target: self,
selector: "drawBlueAnimatedRectangle",
userInfo: nil, repeats: false)
}
func drawRedAnimatedRectangle() {
layer.addSublayer(redRectangleLayer)
redRectangleLayer.animateStrokeWithColor(Colors.red)
}
func drawBlueAnimatedRectangle() {
layer.addSublayer(blueRectangleLayer)
blueRectangleLayer.animateStrokeWithColor(Colors.blue)
NSTimer.scheduledTimerWithTimeInterval(0.40, target: self, selector: "drawArc",
userInfo: nil, repeats: false)
}
func drawArc() {
layer.addSublayer(arcLayer)
arcLayer.animate()
NSTimer.scheduledTimerWithTimeInterval(0.90, target: self, selector: "expandView",
userInfo: nil, repeats: false)
}
func expandView() {
backgroundColor = Colors.blue
frame = CGRectMake(frame.origin.x - blueRectangleLayer.lineWidth,
frame.origin.y - blueRectangleLayer.lineWidth,
frame.size.width + blueRectangleLayer.lineWidth * 2,
frame.size.height + blueRectangleLayer.lineWidth * 2)
layer.sublayers = nil
UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
self.frame = self.parentFrame
}, completion: { finished in
self.addLabel()
})
}
func addLabel() {
delegate?.animateLabel()
}
}
7. Colors.swift
import UIKit
struct Colors {
static let blue = UIColor(red: 46.0 / 255.0, green: 117.0 / 255.0, blue: 146.0 / 255.0, alpha: 1.0)
static let red = UIColor(red: 209.0 / 255.0, green: 42.0 / 255.0, blue: 24.0 / 255.0, alpha: 1.0)
static let white = UIColor.whiteColor()
static let clear = UIColor.clearColor()
}
后记
本篇主要讲述了一种复杂加载动画的实现,感兴趣的给个赞或者关注~~~