5 核心动画框架——CoreAnimation

在前面章节中介绍了有关UIView层的过渡动画与转场动画的应用,使用UIView类有关动画的类方法基本可以满足开发中遇到的大部分需求。然而,UIView层的动画仍然有许多局限性,开发者若想更加自由地进行iOS动画编程还需要使用一个更加底层也更加强大的框架:CoreAnimation。

CoreAnimation框架也被称为核心动画编程框架,它是基于OpenGL与CoreGraphics图像处理框架的一个跨平台的动画框架。如图5-1所示描述了CoreAnimation框架的系统结构。

图5-1 CoreAnimation框架的系统结构

CoreAnimation框架中大多数的动画效果都是基于CALayer类来实现的。任何一个UIView视图中都包含了一个CALayer属性,CALayer对象负责UIView视图的渲染与层级结构。其实CALayer对读者来说并不陌生,在第2章中进行视图控件的圆角、边框、阴影等操作的时候就已经使用了Layer层的属性。

5.1 锚点对视图控件几何位置的影响

CoreAnimation的动画开发是基于Layer层的,要了解CALayer类的使用,首先应该先了解一个概念:锚点。

所有的CALayer对象都有一个anchorPoint属性,这个属性就是CALayer对象的锚点,是CGPoint类型。锚点可以理解为对象动作的参照点,其x,y值的取值范围都是0~1。

CALayer对象默认的锚点值为(0,0),即Layer层的左上角,如果要将锚点设置Layer层的中心,则可以设置anchorPoint的值为(0.5,0.5)。当CALayer层进行动作操作时,执行的动作将以锚点作为参照点,例如进行旋转、平移等操作。图5-2与图5-3所示为设置不同位置锚点的CALayer对象。 图5-2 设置锚点为(0.5,0.5) 图5-3 设置锚点为(0,0) 当对图5-2与图5-3中的视图进行旋转操作时,效果如图5-4与图5-5所示。 图5-4 以视图中心为锚点进行旋转 图5-5 以视图左上角为锚点进行旋转

当一个UIView对象被创建出来后,其内部自带一个CALayer对象,Layer层中主要保存视图的绘制信息,View层则除了整合视图的绘制外,还封装了接收事件与处理用户交互的功能。

5.2 色彩梯度层——CAGradientLayer

CAGradientLayer类是继承于CALayer类的子类,专门用来创建颜色梯度渐变的视图效果。

使用Xcode开发工具创建一个名为CAGradientLayerTest的工程,在ViewController类的viewDidLoad方法中添加如下代码。

  override func viewDidLoad() {
        super.viewDidLoad()
        let layer = CAGradientLayer()
        layer.colors = [UIColor.red.cgColor,UIColor.blue.cgColor]
        layer.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
        layer.position = CGPoint(x: 100, y: 100)
        layer.locations = [0.2]
        layer.startPoint = CGPoint(x: 0, y: 0)
        layer.endPoint = CGPoint(x: 1, y: 1)
        self.view.layer.addSublayer(layer)
    }

上面代码中,CAGradientLayer对象的colors属性用于设置一个颜色梯度数组。bounds属性是CALayer类的基础属性,用于设置Layer层的尺寸。

position属性是CALayer对象用来确定位置的属性,其需要设置一个CGPoint类型的值来确定一个点,position属性的参照点也与Layer层的锚点有关。

locations属性设置色阶梯度的分界线,需要设置成一个数组,数组中元素均为0~1之间并且递增的数值,例如上面代码将其色阶梯度分界线设置为0.2,则红色会渲染Layer层20%尺寸后才开始进行向蓝色渐变的梯度变化。

CAGradientLayer对象的startPoint与endPoint属性分别设置色阶梯度渲染的起始点与结束点,通过这两个属性可以确定色阶梯度渐变的方向。

例如上面代码将起始点设置为(0,0),结束点设置为(1,1),则颜色从Layer层的左上角开始渲染到右下角结束,按左上角到右下角的对角线为渲染方向。

运行工程,效果如图5-2-1所示。

图5-2-1 使用CAGradientLayer类创建的色阶梯度视图层

5.3 视图拷贝层——CAReplicatorLayer

CARepliccatorLayer类可以理解为一个Layer层拷贝容器,其作用并非是进行具体某一种视图的展现,而是拷贝一个已经存在的Layer层对象进行复制渲染。

使用Xcode开发工具创建一个名为CAReplicatorLayerTest的工程,在ViewController类的viewDidLoad方法中添加如下代码。

 override func viewDidLoad() {
        super.viewDidLoad()
        let layer = CALayer()
        layer.bounds = CGRect(x: 0, y: 0, width: 50, height: 50)
        layer.position = CGPoint(x: 50, y: 100)
        layer.backgroundColor = UIColor.red.cgColor
        //创建拷贝容器
        let reLayer = CAReplicatorLayer()
        reLayer.instanceRedOffset = -0.2;
        reLayer.position = CGPoint(x: 0, y: 0)
        reLayer.instanceTransform = CATransform3DMakeTranslation(100, 0, 0)
        reLayer.instanceCount = 3
        reLayer.addSublayer(layer)
        self.view.layer.addSublayer(reLayer)
    }

上面代码中先创建了一个普通的Layer层作为要拷贝的原本,将其作为子Layer添加进CAReplicatorLayer对象中。

CAReplicatorLayer对象的instanceRedOffSet属性设置每个拷贝的副本背景色中红基色的偏移量,与其对应的还有instanceGreenOffset,instanceBlueOffset, instanceAlphaOffset属性分别设置背景色中蓝基色、绿基色和透明度的偏移量。instanceTransform属性设置拷贝视图的3D变化属性,上面代码设置的CATransform3DTranslation(100,0,0)的意义为将每个拷贝Layer层沿x轴正方向移动100个单位。instanceCount属性设置加上原本要拷贝出的层视图个数。 运行工程,效果如图5-6所示。

图5-6 使用CARelicatorLayer类创建的Layer拷贝视图层

5.4 图形渲染层——CAShapeLayer

CAShapeLayer是用于绘制图形的Layer层的,开发者可以使用它进行自定义的图形绘制。

使用Xcode开发工具创建一个名为CAShapeLayerTest的工程,在ViewController类的viewDidLoad方法中添加如下代码。

   override func viewDidLoad() {
        super.viewDidLoad()
        let layer = CAShapeLayer()
        layer.position = CGPoint(x: 0, y: 0)
        let path = CGMutablePath()
        path.move(to: CGPoint(x: 100, y: 100))
        path.addLine(to: CGPoint(x: 300, y: 100))
        path.addLine(to: CGPoint(x: 200, y: 200))
        path.addLine(to: CGPoint(x: 100, y: 100))
        layer.path = path
        layer.strokeColor = UIColor.black.cgColor
        self.view.layer.addSublayer(layer)
    }

CAShapeLayer对象的path属性用于设置图层的绘制路径,通过上面代码的设置将在屏幕上绘制一个三角形的Layer层,运行工程,效果如图5-7所示。

图5-7 CAShapeLayer层绘制的三角形

CAShapeLayer类中还有许多方法用来绘制的图案进行自定义设置,示例代码如下:

   layer.fillColor = UIColor.red.cgColor
        layer.strokeColor = UIColor.blue.cgColor
        layer.strokeStart = 0.3
        layer.strokeEnd = 0.8
        layer.lineWidth = 4

fillColor属性用于设置图形的填充颜色,strokeColor属性用于设置图形的边框线条颜色,strokeStart属性用于设置线条的起点,这个值是一个比例值,为占周长的比例,strokeEnd属性设置线条结束的终点,lineWidth属性设置边框线条的宽度。再次运行工程,效果如图5-8所示。

图5-8 为CAShapeLayer绘制的图形进行自定义设置

5.5 文本绘制层——CATextLayer

CATextLayer用于进行视图上文本的绘制,UIKit框架中最基础的UILabel控件就是基于CATextLayer实现的。使用Xcode开发工具创建一个名为CATextLayerTest的工程,在ViewController类的viewDidLoad方法中添加如下代码。

 override func viewDidLoad() {
        super.viewDidLoad()
        let layer = CATextLayer()
        layer.bounds = CGRect(x: 0, y: 0, width: 320, height: 100)
        layer.position = CGPoint(x: 160, y: 100)
        layer.string = "我是文字Layer"
        layer.fontSize = 25
        layer.foregroundColor = UIColor.red.cgColor
        layer.alignmentMode = kCAAlignmentCenter
        self.view.layer.addSublayer(layer)
    }

上面代码中,CATextLayer的string属性用于设置要显示的文字,fontSize属性设置显示文字的字号,foregroundColor属性设置文字的颜色,alignmentMode属性设置文字的对齐模式。运行工程,效果如图5-9所示。

图5-9 CATextLayer创建的渲染文本的Layer层

除了上面介绍的这些Layer层类,CALayer家族中还有一个十分强大的类CAEmitterLayer,专门用来处理一些粒子效果生成的动画,因其比较复杂,将安排在后面章节单独进行介绍。

5.6 CAAnimation动画体系介绍

通过前面几个小节的介绍,读者可以了解到,通过CALayer开发者可以设置许多视图UI上的属性,CAAnimation框架的作用就是将这些属性的变化都以动画的形式展现出来。由于CALayer相对于UIView可以更灵活地设置更多属性,所以对于CALayer层的动画操作也会更加灵活自由。

CoreAnimation框架中的核心类是CAAnimation,CAAnimation是动画操作的基类,开发者主要使用一些继承自它的子类来进行CALayer层动画的开发。如图5-6-1所示为CAAnimation及其子类的关系结构图。

图5-6-1 CAAnimation及其子类结构图

CAAnimation类分出的3个子类CAPropertyAnimation、CAAnimationGroup、CATransition分别用来处理3种不同类型的动画。CAPropertyAnimation最为常用,视图属性的改变都可以由它来进行动画展示。CAAnimationGroup类用于创建组合动画。CATransition用来创建转场动画。

关于CAPropertyAnimation,它又分出两个子类,CABasicAnimation和CAKeyframeAnimation,这两个子类分别用来处理基础类的动画和关键帧类的动画。

在基础动画CABasicAnimation中又分出一个子类CASpringAnimation专门用来创建阻尼动画。

CAAnimation类主要定义了所有动画操作都会有的共有属性,主要有如下两个属性:

//动画执行的时序模式
open var timingFunction: CAMediaTimingFunction?
//是否动画完成时将动画对象移除
open var isRemovedOnCompletion: Bool

timingFunction属性设置动画执行的时间函数,removedOnCompletion用于设置当动画播放完成后是否将动画对象从CALayer上除掉。timingFunction属性可设的参数值如下所示:

//线性执行
public let kCAMediaTimingFunctionLinear: String
//淡入 在动画开始时 淡入效果
public let kCAMediaTimingFunctionEaseIn: String
//淡出 在动画结束时 淡出效果
public let kCAMediaTimingFunctionEaseOut: String
//淡入淡出
public let kCAMediaTimingFunctionEaseInEaseOut: String
//默认效果
public let kCAMediaTimingFunctionDefault: String

使用如下的示例代码方法进行CAMediaTimingFunction对象的创建。

CAMediaTimingFunction(name: kCAMediaTimingFunctionDefault)

CAPropertyAnimation类中封装了用于属性变化的动画的基类属性,主要代码如下:

public convenience init(keyPath path: String?)
open var isAdditive: Bool
open var isCumulative: Bool
open var valueFunction: CAValueFunction?

在上面的方法和属性中,animationWithKeyPath方法用于创建并初始化动画对象,其中path参数即为要执行动画的属性。

例如如果要执行背景色渐变的动画,则path可设置为字符串backgroundColor。additive设置动画执行的状态是否叠加,如果设置为布尔值假,当执行移动动画时,如果执行两次,两次动画都会从视图的原始位置进行移动,如果设置布尔值真,则第2次移动动画的起点会从第1次移动的终点开始。

cumulative属性与重复执行的动画有关,如果设置为布尔值假,则重复动画每次执行都会从原始状态开始执行,设置为布尔值真则重复动画的每次效果都会叠加。

ValueFunction这个属性比较特殊,其专门用来处理transform变换类的动画,在后面小节的应用中会向读者详细介绍这个属性的应用。

5.7 使用CABasicAnimation创建基础动画

前面小节介绍了许多基础知识,从CALayer层的基本用法到CAAnimation的基类属性。从本节开始将进行一些CoreAnimation动画开发的实践操练。CABasicAnimation是CoreAnimation动画框架中最常用的动画执行类。使用Xcode开发工具创建一个名为CABasicAnimationTest的工程,在ViewController类中声明一个CALayer类的对象,如下所示:

private var layer:CALayer?

在viewDidLoad方法中进行layer属性的创建和初始化相关操作,如下所示:

    override func viewDidLoad() {
        super.viewDidLoad()
        layer = CALayer()
        layer?.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
        layer?.position = CGPoint(x: 160, y: 100)
        layer?.backgroundColor = UIColor.red.cgColor
        self.view.layer.addSublayer(layer!)
    }

在ViewController类中实现touchesBegan方法,如下所示:

 override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        //背景色渐变动画
        let ani = CABasicAnimation(keyPath: "backgroundColor")
        //从红色开始
        ani.fromValue = UIColor.red.cgColor
        //渐变成蓝色
        ani.toValue = UIColor.blue.cgColor
        //时间2秒
        ani.duration = 2
        //执行动画
        layer?.add(ani, forKey: "")
    }

上面代码设置为背景色的变化添加渐变动画,fromValue属性设置起始的颜色。toValue属性设置要渐变成的颜色。duration属性设置动画执行的时间。调用CALayer类的add(animation:forKey)方法进行动画动作的添加和执行,这个方法中第1个参数为要执行的动画动作对象,第2个参数将为这个动作设置一个标识符。

CABasicAnimation类中还有byValue这样一个属性,fromValue和byValue两个属性值的差异如下所示。

  • fromValue和toValue都不为空则动画的值由fromValue变化到toValue。
  • fromValue和byValue都不为空则动画的值由fromValue变化到fromValue+ byValue。
  • byValue和toValue都不为空:动画的值由toValue-byValue变化到toValue。
  • 只有fromValue不为空则动画的值由fromValue变化到layer的当前状态值。
  • 只有toValue不为空:动画的值由layer当前的值变化到toValue。
  • 只有byValue不为空:动画的值由layer当前的值变化到layer当前的值+byValue。

如果要执行transform变换的相关动画,例如使视图沿z轴进行旋转(即在屏幕平面旋转),使用如下的代码。

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let ani = CABasicAnimation(keyPath: "transform")
        //从0度开始
        ani.fromValue = 0
        //旋转到180度
        ani.toValue = M_PI
        //时间2秒
        ani.duration = 2
        //设置为z轴旋转
        ani.valueFunction = CAValueFunction(name: kCAValueFunctionRotateZ)
        //执行动画
        layer?.add(ani, forKey: "")
    }

上面在创建CABasicAnimation对象时,设置其KeyPath为transform。但是具体的变换方式是由valueFunction属性决定的,CAValueFunction

function(name:kCAValueFunctionRotateZ)创建沿z轴旋转的动画方式,还可以通过下面的创建参数进行其他transform变换方式的设置: //设置沿x轴旋转操作 kCAValueFunctionRotateX //设置沿y轴旋转操作 kCAValueFunctionRotateY //设置沿z轴旋转操作 kCAValueFunctionRotateZ //设置沿x轴缩放操作 kCAValueFunctionScaleX //设置沿y轴缩放操作 kCAValueFunctionScaleY //设置沿z轴缩放操作 kCAValueFunctionScaleZ //设置沿x轴平移操作 kCAValueFunctionTranslateX //设置沿y轴平移操作 kCAValueFunctionTranslateY //设置沿z轴平移操作 kCAValueFunctionTranslateZ CASpringAnimation动画和CABaiscAnimation动画相比只是多了一些关于阻尼操作的参数,开发者可以设置这些参数在动画的展现过程中添加类似弹簧的阻尼效果,可用属性如下所示。

//这个属性设置类似弹簧重物的质量,会影响惯性,所以必须大于0 默认为1
 open var mass: CGFloat
//设置弹簧的刚度系数,必须大于0,默认为100,这个越大则回弹越快
open var stiffness: CGFloat
//阻尼系数默认为10,必须大于0,这个值越大回弹的幅度越小
open var damping: CGFloat
//初始速度
open var initialVelocity: CGFloat
//获取动画停下来需要的时间 只读属性
open var settlingDuration: CFTimeInterval { get }

5.8 使用CAKeyframeAnimation类创建关键帧动画

CAKeyframeAnimation动画也被称为关键帧动画,也是继承自CAPropertyAnimation,用于创建Layer层属性变化相关的动画。

比CABasicAnimation更加强大的是,CABasicAnimation创建的属性变化动画只能通过fromValue、byValue和toValue这些属性设置Layer属性变化的起始值和结束值。

而CAKeyframeAnimation可以通过设置动画关键帧的方式自由控制整个动画的过程。 使用Xcode开发工具创建一个名为CAKeyframeAnimationTest的工程,在ViewController类中声明一个CALayer类型的属性。

private var layer:CALayer?

在viewDidLoad方法中进行相关的初始化操作,如下所示。

 override func viewDidLoad() {
        super.viewDidLoad()
        layer = CALayer()
        layer?.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
        layer?.backgroundColor = UIColor.red.cgColor
        layer?.position = CGPoint(x: 50, y: 100)
        self.view.layer.addSublayer(layer!)
    }

在ViewController类中实现touchesBegan方法进行效果的测试。

  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let ani = CAKeyframeAnimation(keyPath: "position")
        ani.values = [CGPoint(x: 50, y: 100),CGPoint(x: 120, y: 100),CGPoint(x: 120, y: 200),CGPoint(x: 200, y: 200)]
        ani.keyTimes = [0,0.5,0.8,1]
        ani.duration = 3
        layer?.add(ani, forKey: "")
    }

在上面的代码中,CAKeyframeAnimation类的values属性用于设置关键帧数组,例如上面进行的动画动作为position位置的移动,在这个数组中需要设置为动画过程中的位置转折点。CAKeyframeAnimation类的keyTimes属性设置每段动画的时间占比,其中值的取值范围为0~1之间并且是递增的。

5.9 CALayer层的转场动画——CATransition

CATransition动画用来处理CALayer层的转场效果,和CAPropetyAnimation动画不同之处在于CAPropetyAnimation动画是当CALayer对象的属性改变时展示动画效果,而CATransition是当CALayer对象出现时展现动画效果。

使用Xcode开发工具创建一个名为CATransitionTest的工程,直接在ViewController类中实现如下方法。

  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let layer = CALayer()
        layer.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
        layer.position = CGPoint(x: 100, y: 100)
        layer.backgroundColor = UIColor.red.cgColor
        let ani = CATransition()
        ani.type = kCATransitionPush
        ani.subtype = kCATransitionFromRight
        ani.duration = 3
        layer.add(ani, forKey: "")
        self.view.layer.addSublayer(layer)
    }

在上面的代码中,CATransition类的type属性设置转场动画的类型,可选的参数字符串如下所示。

//淡入
public let kCATransitionFade: String
//移入
public let kCATransitionMoveIn: String
//压入
public let kCATransitionPush: String
//溶解
public let kCATransitionReveal: String
CATransition类的subtype属性设置动画执行的方向,可选的参数字符串如下所示。
//从右侧进
public let kCATransitionFromRight: String
//从左侧进
public let kCATransitionFromLeft: String
//从上侧进
public let kCATransitionFromTop: String
//从下侧进
public let kCATransitionFromBottom: String

5.10 CALayer层的组合动画——CAAnimationGroup

CAAnimationGroup类并没有定义特定的动画类型,可以把它理解为一个动画容器,通过CAAnimationGroup可以将上面小节所介绍的动画效果进行组合展示。

使用Xcode开发工具创建一个名为CAAnimationGroupTest的工程,在ViewController类中声明如下属性。

var layer:CALayer?

在viewDidLoad中作如下初始化操作。

   override func viewDidLoad() {
        super.viewDidLoad()
        layer = CALayer()
        layer?.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
        layer?.position = CGPoint(x: 100, y: 100)
        layer?.backgroundColor = UIColor.red.cgColor
        self.view.layer.addSublayer(layer!)
    }

在ViewController类中实现touchesBegan方法。

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let ani1 = CABasicAnimation(keyPath: "backgroundColor")
        ani1.toValue = UIColor.blue.cgColor
        let ani2 = CABasicAnimation(keyPath: "position")
        ani2.toValue = CGPoint(x: 200, y: 300)
        let group = CAAnimationGroup()
        group.duration = 3
        group.animations = [ani1,ani2]
        layer?.add(group, forKey: "")
    }

CAAnimationGeoup中的animations数组中需要设置为CAAnimation的对象,设置的动画效果将会被同时组合展示,例如上面代码会将色块在3秒内位置移动到(200,300)的点并且颜色渐变为蓝色。

5.11 CATransform3D变换的应用

任何CALayer对象都有transform这样一个属性,这个属性是用来设置CALayer对象视图渲染的数学变换效果的。在前面章节中有示例,transform属性是可以支持CoreAnimation动画效果的。

CATransform3D是一个四维矩阵的结构体,可以实现视图的平移、旋转、景深旋转、缩放、镜像翻转等操作。

使用Xcode开发工具创建一个名为CATransform3DTest的工程,为了便于演示效果,向工程中添加一张图片素材。

在ViewController类中的viewDidLoad方法中添加如下代码实现视图的平移变换。

   override func viewDidLoad() {
        super.viewDidLoad()
        let image1 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        image1.image = #imageLiteral(resourceName: "image")
        let image2 = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        image2.image = #imageLiteral(resourceName: "image")
        let trans = CATransform3DTranslate(image2.layer.transform, 100, 100, 0)
        image2.layer.transform = trans
        self.view.addSubview(image1)
        self.view.addSubview(image2)
    }

在上面的代码中创建了两个一样的UIImageView对象,并且将其设置在相同的位置,对image2对象进行了平移变换,CATransform3DTranslate()方法用于创建平移变换对象,这个方法的第1个参数为原CATransform3D对象,第2个参数为平移的x坐标值,第3个参数为平移的y坐标值,第4个参数为平移的z坐标值。运行工程,效果如图6-15所示。

使用如下代码进行缩放变换。

let tran = CATransform3DScale(trans, 0.5, 2, 0)

上面方法第1个参数为原始的CATransform3D对象,第2个参数为x轴方向的缩放比,第3个参数为y轴方向的缩放比,第4个参数为z轴方向的缩放比。运行工程,效果如图6-16所示。

使用下面代码进行旋转变换。

let tran2 = CATransform3DRotate(tran, CGFloat(M_PI_4), 0, 0, 1)

上面方法中第1个参数为原始的CATransform3D对象,第2个参数为要旋转的角度,为弧度制。第3个参数为旋转方向在x轴上的分量,第4个参数为旋转方向在y轴上的分量,第5个参数为旋转方向,在z轴上的分量,运行工程,效果如图6-17所示。 使用下面方法进行带景深效果的旋转变换。

var trans = CATransform3DTranslate(image2.layer.transform, 100, 100, 0)
trans.m34 = -1/600.0
image2.layer.transform = CATransform3DRotate(trans, CGFloat(M_PI_4), 0, 1, 0)

CATransform3D结构的m34参数设置景深的参数,运行工程,效果如图6-18所示。 下面方法进行旋转镜像变换。 let tran = CATransform3DRotate(image1.layer.transform, CGFloat(M_PI_4), 0, 0, 1) image1.layer.transform = tran image2.layer.transform = CATransform3DInvert(tran) CATransform3DInvert方法用于将一个变换效果进行翻转,运行效果如图6-19所示。 需要注意,以上介绍的所有transform变换都可以进行CoreAnimation的动画展示。