iOS开发

动画示例(九) —— 一种复杂加载动画的实现 (一)

2018-08-25  本文已影响119人  刀客传奇

版本记录

版本号 时间
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,项目已经包含初始化此图层的代码以及您将在动画中使用的所有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)
}

这是很多代码,但它很好地分解了。 这是发生了什么:

每个后续动画的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) 
}

上面的代码执行以下操作:

注意:为动画找到合适的持续时间或延迟需要一些试验和错误,并且可能意味着好动画和梦幻动画之间的区别。 我鼓励你修改你的动画,让它们看起来很完美。 这可能需要一些时间,但值得!

接下来,将以下函数添加到类的底部:

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()
}

在您添加此代码之前创建的计时器会在椭圆停止摆动并且出现三角形的所有角时调用此函数。 这里更详细地看一下这个函数:

现在打开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)
}

这通过应用CABasicAnimationOvalLayer设置回其初始路径ovalPathSmall。 这与expand()完全相反,您在动画开始时调用它。

构建并运行您的应用程序,动画完成后,三角形是唯一应留在屏幕上的东西:


Drawing The Container - 绘制容器

在下一部分中,您将为矩形容器的绘图设置动画以创建容器。 为此,您将使用RectangleLayerstroke属性。 你会做两次,使用红色和蓝色作为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周围的strokeCAShapeLayerstrokeEnd键指示停止描边的路径周围。 通过将此属性设置为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()
    })
}

这是该方法的作用:

将以下函数添加到类的底部:

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. 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()
}

后记

本篇主要讲述了一种复杂加载动画的实现,感兴趣的给个赞或者关注~~~

上一篇 下一篇

猜你喜欢

热点阅读