浅析iOS Core Animation

原文地址

简介

分析的知识点:

  • layer tree
  • CGAffineTransform
  • addArc
  • calculationMode
  • CAAnimationGroup
  • UIView.animate animateKeyframes、CABasicAnimation、CAKeyframeAnimation

提到Core Animation,就会想到CALayer
修改一个View的layer后,会用Core Graphics框架来渲染。
Core Animation有三个tree:

  • model layer tree(简称layer tree),存储动画的最终状态值,当改变layer的属性值时,使用这一个
  • presentation layer tree,在动画过程中,使用这个来获取当前动画过程中的值,但不要改变他的值
  • rendering tree,最后一个对Core Animation而言是私有的。
一个window中的layers
window的layer trees

UIView常用动画

透明度变化:

    UIView.animate(withDuration: 0.3, animations: {
      self.pandaImageView.alpha = 1
    }) { _ in
      self.pandaImageView.removeFromSuperview()
    }

或者更改一下frame,直接这样调用,使用非常方便。再看下面这一个,放大缩小再复原:

UIView.animate(withDuration: 0.3, animations: {
      self.pandaImageView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
    }) { _ in
      UIView.animate(withDuration: 0.3, animations: {
        self.pandaImageView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
      }, completion: { _ in
        UIView.animate(withDuration: 0.3, animations: {
          self.pandaImageView.transform = CGAffineTransform(scaleX: 1, y: 1)
        }, completion: { _ in
          self.view.removeFromSuperview()
        })
      })
    }

感觉也没啥问题,就是嵌套多了一点,也有点丑,针对这种嵌套,可以这样解决:

let totalTime = toBig + toSmall + dismissView
UIView.animateKeyframes(withDuration: totalTime, delay: 0, options: [], animations: {
      UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: toBig/totalTime, animations: {
        self.frontView.transform = CGAffineTransform(scaleX: 1.25, y: 1.25)
      })
      UIView.addKeyframe(withRelativeStartTime: toBig/totalTime, relativeDuration: toSmall, animations: {
        self.frontView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
      })
      UIView.addKeyframe(withRelativeStartTime: (toBig + Animation.toSmall)/totalTime, relativeDuration: dismissView, animations: {
        self.alpha = 0
      })
    }) { _ in
      self.removeFromSuperview()
    }

: withRelativeStartTimerelativeDuration 这两个值都是0.0到1.0之间的数值, 需要除以总动画时间。

这种比刚才的写法要好很多,不用嵌套多层,但解决不了复杂的动画。如果在做放大缩小前还有别的动画呢,比如先沿特定曲线路径移动,再放大缩小,可能这种写法就满足不了,对于复杂动画构建及时间函数控制,看下文。

基本动画

让一个图片做直线运动:

func lineAnimation() {
    let animation = CABasicAnimation()
    animation.keyPath = "position.x";
    animation.fromValue = kScreenWidth/2;
    animation.toValue = kScreenWidth;
    animation.duration = 1;
    pandaImageView.layer.add(animation, forKey: "line")
    pandaImageView.layer.position = CGPoint(x: kScreenWidth, y: kScreenHeight/2)
  }

动画的键路径"position.x",在x轴上的直线运动,其它的键可以查看Core Animation键路径的列表
动画开始后,图片从中心位置移到了屏幕边缘,动画结束后,图片回到了中心点。要想让动画结束后保持最后的状态,有两种方法:

  • model layer上更新属性,推荐这种,这样presentation layermodel layer就同步了
  • 设置动画的 fillMode 属性为 kCAFillModeForward 以留在最终状态,并设置removedOnCompletionNO 以防止它被自动移除
    采用第二种方法会将已完成的动画保持在layer上,有额外开销,还需要考虑何时需要手动remove掉,比如在应用切后台时。

沿路径动画

做有弧度的曲线或者圆形动画时,可以用CAKeyframeAnimation,指定path,逆时针做90度的圆弧运动:

    let keyframeAnimation = CAKeyframeAnimation(keyPath: "position")
    let path = CGMutablePath()
    path.addArc(center: CGPoint(x: 0, y: 0), radius: radius, startAngle: 0, endAngle: 1.5 * .pi, clockwise: true)
    keyframeAnimation.path = path
    keyframeAnimation.calculationMode = kCAAnimationPaced
    keyframeAnimation.duration = 2.0;
    pandaImageView.layer.add(keyframeAnimation, forKey: "round")
    pandaImageView.layer.position = CGPoint(x: 0, y: 0)
CoreAnimation-point.png

CGAffineTransform

pandaImageView做了一个CGAffineTransform(translationX: kScreenWidth/2, y: kScreenHeight/2), CGAffineTransform为一个变换,可以把一个坐标系映射到另外一个坐标系,平移、放缩、旋转都可以做到,详细请看, CGAffineTransform默认的anchor point为中心点(0.5, 0.5),这里我们不用改,正是想要的,在应用上这个transform后, addArc圆的圆心就变成了(0,0)

addArc

addArcclockwise,有如下解释:

true to make a clockwise arc; false to make a counterclockwise arc.

clockwise为true时,path的方向为逆时针,而算角度,始终以逆时针方向算。

calculationMode

calculationMode能决定动画的时间曲线, kCAAnimationPaced为匀速,其它的几种mode可以查看官方文档,默认为kCAAnimationLinear.

A->B->C->D->A,连起来的动画,需要一个path:

    path.addLine(to: CGPoint(x: radius, y: 0))
    path.addArc(center: CGPoint(x: 0, y: 0), radius: radius, startAngle: 0, endAngle: 1.5 * .pi, clockwise: true)
    path.addLine(to: CGPoint(x: -radius/2, y: 0))
    path.addLine(to: CGPoint(x: 0, y: 0))

放缩动画

    let keyframeAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
    keyframeAnimation.values = [1.2, 0.8, 1]
    keyframeAnimation.duration = 1.0;
    keyframeAnimation.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)]

关于timing function,看下图:

timing-function.png

动画组

   let roundAnimation = self.roundAnimation()
    let scaleAnimation = self.scaleAnimation()
    scaleAnimation.beginTime = roundAnimation.duration
    let groupAnimation = CAAnimationGroup()
    groupAnimation.animations = [roundAnimation, scaleAnimation]
    groupAnimation.duration = roundAnimation.duration + scaleAnimation.duration
    pandaImageView.layer.add(groupAnimation, forKey: "group")

指定好开始时间和持续时间,可以使用一个动画组
动画效果如图:


group-animation.gif

对于开篇提到的问题,用动画组合可以很好的解决问题

参考文章

objccn动画解释
Core Animation Programming Guide

你可能感兴趣的:(浅析iOS Core Animation)