iOS动画教你编写Slack的Loading动画进阶篇

2020-01-15 19:08:53王振洲

第四步线条恢复的原来长度的动画

这里我们还是使用CABasicAnimation基础动画,keyPath作用于线条的strokeEnd属性,让strokeEnd从0到1来实现线条长短的动画,下面是效果图和代码:

iOS,Slack,Loading


/**
   线的第三步动画,线由短变长
   */
  private func lineAnimationThree() {
    //线移动的动画
    let lineAnimationFour         = CABasicAnimation.init(keyPath: "strokeEnd")
    lineAnimationFour.beginTime      = CACurrentMediaTime() + duration
    lineAnimationFour.duration      = duration/4
    lineAnimationFour.fillMode      = kCAFillModeForwards
    lineAnimationFour.removedOnCompletion = false
    lineAnimationFour.fromValue      = 0
    lineAnimationFour.toValue       = 1
    for i in 0...3 {
      if i == 3 {
        lineAnimationFour.delegate = self
      }
      let lineLayer = lines[i]
      lineLayer.addAnimation(lineAnimationFour, forKey: "lineAnimationFour")
    }
  }

最后一步需要将动画组合起来

关于动画组合我没用到CAAnimationGroup,因为这些动画并不是加到同一个layer上,再加上动画类型有点多加起来也比较麻烦,我就通过动画的beginTime属性来控制动画的执行顺序,还加了动画暂停和继续的功能,效果和代码见下图:

iOS,Slack,Loading

 


//MARK: Public Methods
  /**
   开始动画
   */
  func startAnimation() {
    angleAnimation()
    lineAnimationOne()
    lineAnimationTwo()
    lineAnimationThree()
  }

  /**
   暂停动画
   */
  func pauseAnimation() {
    layer.pauseAnimation()
    for lineLayer in lines {
      lineLayer.pauseAnimation()
    }
    status = .pause
  }

  /**
   继续动画
   */
  func resumeAnimation() {
    layer.resumeAnimation()
    for lineLayer in lines {
      lineLayer.resumeAnimation()
    }
    status = .Animating
  }

  extension CALayer {
  //暂停动画
  func pauseAnimation() {
    // 将当前时间CACurrentMediaTime转换为layer上的时间, 即将parent time转换为localtime
    let pauseTime = convertTime(CACurrentMediaTime(), fromLayer: nil)
    // 设置layer的timeOffset, 在继续操作也会使用到
    timeOffset  = pauseTime
    // localtime与parenttime的比例为0, 意味着localtime暂停了
    speed     = 0;
  }

  //继续动画
  func resumeAnimation() {
    let pausedTime = timeOffset
    speed     = 1
    timeOffset   = 0;
    beginTime   = 0
    // 计算暂停时间
    let sincePause = convertTime(CACurrentMediaTime(), fromLayer: nil) - pausedTime
    // local time相对于parent time时间的beginTime
    beginTime   = sincePause
  }
}

//MARK: Animation Delegate
  override func animationDidStart(anim: CAAnimation) {
    if let animation = anim as? CABasicAnimation {
      if animation.keyPath == "transform.rotation.z" {
        status = .Animating
      }
    }
  }

  override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
    if let animation = anim as? CABasicAnimation {
      if animation.keyPath == "strokeEnd" {
        if flag {
          status = .Normal
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(interval) * Int64(NSEC_PER_SEC)), dispatch_get_main_queue(), {
            if self.status != .Animating {
              self.startAnimation()
            }
          })
        }
      }
    }
  }

   //MARK: Override
  override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    switch status {
    case .Animating:
      pauseAnimation()
    case .pause:
      resumeAnimation()
    case .Normal:
      startAnimation()
    }
  }