iOS中的CoreAnimation介绍

CoreAnimation

Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍。也就是说,使用少量的代码就可以实现非常强大的功能。Core Animation可以用在Mac OS X和iOS平台,Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。要注意的是,Core Animation是直接作用在CALayer上的,并非UIView

使用Core Animation创建动画不仅简单,而且具有更好的性能,原因有2个:

  • Core Animation动画在单独的线程中完成,不会阻塞主线程
  • Core Animation动画只会重绘界面变化的部分(局部刷新)

CAAnimation

20151117204956155.png

所有动画对象的父类,负责控制动画的持续时间和速度,是个抽象类,不能直接使用,不具备动画效果,应该使用它具体的子类.

  • CAAnimation
open class CAAnimation : NSObject, NSSecureCoding, NSCopying, CAMediaTiming, CAAction {
    open class func defaultValue(forKey key: String) -> Any?
    // 返回指定的属性值是否可以归档
    open func shouldArchiveValue(forKey key: String) -> Bool
    open var timingFunction: CAMediaTimingFunction?
    open var delegate: CAAnimationDelegate?
    open var isRemovedOnCompletion: Bool
}
  • 速度控制函数(CAMediaTimingFunction)
public let kCAMediaTimingFunctionLinear: String //(线性):匀速,给你一个相对静态的感觉
public let kCAMediaTimingFunctionEaseIn: String //(渐进):动画缓慢进入,然后加速离开
public let kCAMediaTimingFunctionEaseOut: String //(渐出):动画全速进入,然后减速的到达目的地
public let kCAMediaTimingFunctionEaseInEaseOut: String // (渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。这个是默认的动画行为。
public let kCAMediaTimingFunctionDefault: String
  • CAAnimationDelegate

监听动画的开始和完成

public protocol CAAnimationDelegate : NSObjectProtocol {
    optional public func animationDidStart(_ anim: CAAnimation) // 动画开始时调用
    optional public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) // 动画结束后调用
}
  • isRemovedOnCompletion

默认为true,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为false,不过还要设置fillMode为kCAFillModeForwards.

  • CAMediaTiming
public protocol CAMediaTiming {
    /* The begin time of the object, in relation to its parent object, if
     * applicable. Defaults to 0. */
    public var beginTime: CFTimeInterval { get set }

    // 动画持续的时间
    public var duration: CFTimeInterval { get set }
    
    /* The rate of the layer. Used to scale parent time to local time, e.g.
     * if rate is 2, local time progresses twice as fast as parent time.
     * Defaults to 1. */
    public var speed: Float { get set }

    /* Additional offset in active local time. i.e. to convert from parent
     * time tp to active local time t: t = (tp - begin) * speed + offset.
     * One use of this is to "pause" a layer by setting `speed' to zero and
     * `offset' to a suitable value. Defaults to 0. */
    public var timeOffset: CFTimeInterval { get set }

    // 动画的次数
    public var repeatCount: Float { get set }

    /* The repeat duration of the object. Defaults to 0. */
    public var repeatDuration: CFTimeInterval { get set }

    /* When true, the object plays backwards after playing forwards. Defaults
     * to NO. */
    public var autoreverses: Bool { get set }

    /* Defines how the timed object behaves outside its active duration.
     * Local time may be clamped to either end of the active duration, or
     * the element may be removed from the presentation. The legal values
     * are `backwards', `forwards', `both' and `removed'. Defaults to
     * `removed'. */
    public var fillMode: CAMediaTimingFillMode { get set }
}
  • 填充模式(fillMode)
extension CAMediaTimingFillMode {
     // 当动画结束后,layer会一直保持着动画最后的状态
    public static let forwards: CAMediaTimingFillMode
    // 在动画开始前,只需要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始。 
    public static let backwards: CAMediaTimingFillMode
    // 这个其实就是上面两个的合成.动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态.
    public static let both: CAMediaTimingFillMode
    // 这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态 
    public static let removed: CAMediaTimingFillMode
}

CAPropertyAnimation

CAPropertyAnimation它是CAAnimation的一个子类,它代表属性动画。当需要使用属性动画时应该使用两个子类CABasicAnimation、CAKeyframeAnimation。

open class CAPropertyAnimation : CAAnimation {
    // 便捷的初始化方法
    public convenience init(keyPath path: String?)
    // CALayer的动画属性名
    open var keyPath: String?
    // 该属性指定该属性动画是否以当前动画效果为基础
    open var isAdditive: Bool
    // 该属性指定动画是否为累加效果
    open var isCumulative: Bool
    //该属性值是一个CAValueFunction对象,该对象负责对属性改变的插值计算,系统已经提供了默认的插值计算方式,因此一般无须指定该属性
    open var valueFunction: CAValueFunction?
}

CABasicAnimtion

CABasicAnimation为layer的属性提供了基本的单一关键帧动画。创建CABasicAnimation对象,通过使用父类提供的便捷构造方法public convenienceinit(keyPath path: String?),只需要指定能够在渲染树中进行动画的属性keyPath

open class CABasicAnimation : CAPropertyAnimation {
     open var fromValue: Any? // keyPath相应属性的初始值
     open var toValue: Any?   // keyPath相应属性的结束值
     open var byValue: Any?  //相应属性的相对插值, 增加多少值
}

如:创建位置移动基本动画对象:

let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = [0, 0]
animation.toValue = [100, 100]

keyPath的值可以为

anchorPoint
backgroundColor
backgroundFilters
borderColor
borderWidth
bounds
compositingFilter
contents
contentsRect
cornerRadius
doubleSided
filters
frame
hidden
mask
masksToBounds
opacity
position
shadowColor
shadowOffset
shadowOpacity
shadowPath
shadowRadius
sublayers
sublayerTransform
transform
zPosition

详细内容可以参考这里 Appendix BCore Animation Programming Guide,、 Appendix C

CABasicAnimation可以看做是一种CAKeyframeAnimation的简单动画,因为它只有头尾的关键帧(keyframe)。当设置了CABasicAnimation的起点与终点值后,中间的值都是通过插值方式计算出来的,插值计算是通过timingFunction来指定,timingFunction默认为空,使用liner(匀速运动)。

例如,当我们设置了一个position的动画,设置了开始值PointA与结束值PointB,它们的运动先计算PointA与PointB的中间运动值PointCenter,而PointCenter是由timingFunction来指定值的,并且动画默认是直线匀速运动的。

使用流程

1、创建CALayer图层
2、初始化一个CABasicAnimation对象,给对象设置相关的属性
3、将基本动画对象添加到CALayer对象中就可以开始动画了

背景色动画

 let animation = CABasicAnimation(keyPath: "backgroundColor")
 animation.fromValue = UIColor.red.cgColor
 animation.toValue = UIColor.cyan.cgColor
 animation.duration = 1.0
 layer.add(animation, forKey: nil)

实现很简单,但是这里会发现一个有趣的现象,即动画结束之后,layer的颜色又回到了最初的状态。如下

2018-11-22 16-48-23.2018-11-22 16_48_41.gif

为什么动画结束后返回原状态?

首先需要搞明白一点,layer动画运行的过程是怎样的?其实在我们给一个视图添加layer动画时,真正移动并不是视图本身,而是 presentation layer 的一个缓存。动画开始时 presentation layer开始移动,原始layer隐藏,动画结束时,presentation layer从屏幕上移除,原始layer显示。这就解释了为什么我们的视图在动画结束后又回到了原来的状态,因为它根本就没动过。

如果想防止回到最初状态也简单

animation.isRemovedOnCompletion = false
animation.fillMode = kCAFillModeForwards

kCAFillModeForwards该值表示动画即使之后layer的状态将保持在动画的最后一帧,而isRemovedOnCompletion的默认属性值是 true,所以为了使动画结束之后layer保持结束状态,应将isRemovedOnCompletion设置为false。

但是要真正实现layer的最终状态,那么需要修改layer对应的属性。使用上面的属性设置仅仅只是保证了动画结束显示当前状态,但是layer本身并为发生改变。

停止和恢复动画

func pauseLayer(_ layer: CALayer) {
    let pausedTime = layer.convertTime(CACurrentMediaTime(), from: nil)
    layer.speed = 0.0
    layer.timeOffset = pausedTime;
}

func resumeLayer(_ layer: CALayer) {
    let pausedTime = layer.timeOffset
    layer.speed = 1.0
    layer.timeOffset = 0.0
    layer.beginTime = 0.0
    let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
    layer.beginTime = timeSincePause
}

去除正在执行的动画

正常情况下,显示动画会从开始执行一直到完成,但是我们可以根据需求停止正在执行的动画。

   // 去除layer上所有的动画
   open func removeAllAnimations()

   // 去除指定key值的动画
   open func removeAnimation(forKey key: String)

实战

移动动画

 func positionAnimation() {
     let animation = CABasicAnimation(keyPath: "position")
     // 对CGPoint进行包装
     animation.fromValue = NSValue(cgPoint: layer.position)
     animation.toValue = NSValue(cgPoint: CGPoint(x: 300, y: 300))
     animation.duration = 1.0
     animation.isRemovedOnCompletion = false
     animation.fillMode = kCAFillModeForwards
     // 动画的次数
     animation.repeatCount = 5
     layer.add(animation, forKey: nil)
 }

效果如下

2018-11-22 17-18-10.2018-11-22 17_18_25.gif

缩放动画

  • 使用bounds进行缩放
 func scaleAnimation() {
     let animation = CABasicAnimation(keyPath: "bounds.size")
     animation.fromValue = NSValue(cgSize: layer.bounds.size)
     animation.toValue = NSValue(cgSize: CGSize(width: 200, height: 200))
     animation.duration = 1.0
     animation.repeatCount = Float.infinity // 无限次执行动画
     layer.add(animation, forKey: nil)
 }
  • 使用transfrom进行缩放
 func transformsScaleAnimation() {
     let animation = CABasicAnimation(keyPath: "transform.scale")
     animation.toValue = 2 // 缩放倍率
     animation.duration = 1.0
     animation.repeatCount = Float.infinity
     layer.add(animation, forKey: nil)
 }

效果如下

2018-11-22 17-45-26.2018-11-22 17_45_39.gif

圆角动画

 func cornerRadiusAnimation() {
     let animation = CABasicAnimation(keyPath: "cornerRadius")
     animation.toValue = 50
     animation.duration = 2.0
     animation.repeatCount = Float.infinity
     layer.add(animation, forKey: nil)
  }

效果如下

2018-11-22 17-50-52.2018-11-22 17_51_11.gif

边框动画

  func borderAnimation() {
      let animation = CABasicAnimation(keyPath: "borderWidth")
      animation.toValue = 10
      animation.duration = 1
      animation.repeatCount = Float.infinity
      layer.add(animation, forKey: nil)
}

效果如下

2018-11-22 17-54-46.2018-11-22 17_54_57.gif

旋转动画

 func rotationAnimation() {
     let animation = CABasicAnimation(keyPath: "transform.rotation.z")
     animation.toValue = CGFloat.pi * 2
     animation.duration = 2.0
     animation.repeatCount = Float.infinity
      // 设置动画速度
      animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
      layer.add(animation, forKey: nil)
  }

效果如下

2018-11-23 12-55-45.2018-11-23 12_56_05.gif

晃动动画

当输入内容错误时,有些app使用了晃动效果提示用户错误,对于UIView而言看这里UIView shake animation

 func shakeAnimation() {
     let midX = layer.position.x
     let midY = layer.position.y

     let animation = CABasicAnimation(keyPath: "position")
     animation.duration = 0.06
     animation.repeatCount = 4
     animation.autoreverses = true
     animation.fromValue = CGPoint(x: midX - 10, y: midY)
     animation.toValue = CGPoint(x: midX + 10, y: midY)
     layer.add(animation, forKey: nil)
 }

效果如下


2018-11-23 13-03-13.2018-11-23 13_03_44.gif

CAKeyframeAnimation

CAKeyframeAnimation是CAPropertyAnimation的子类,支持关键帧的属性动画,该动画最大的特点在于可通过values属性设置多个关键帧,通过多个关键帧可以指定动画的各个阶段的关键值。

属性

  • 关键帧值(Providing keyframe values)
 // 关键帧值 
 open var values: [Any]?
 // 路径
 open var path: CGPath?

values

是关键帧动画最重要的一部分,该值定义了动画执行的行为,它是一个数组,里面的元素理解为”关键帧"(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一关键帧。

在添加到数组之前,需要注意:有些对象可以直接添加到数组当中,但是有些对象必须在被添加之前需要进行包装,比如:有些对象需要包装为id类型,而标量数据类型或者结构体必须被包装成为对象,例如:

1:如果属性是CGRect类型(比如: bounds and frame 属性),应该包装每一个矩形成为NSValue对象。
2:对于layertransform属性,需要包装每一个值CATransform3D成为NSValue对象。
3:对于borderColor属性,在被添加到数组之前,需要CGColorRef数据类型
4:对于CGFloat值,包装为NSNumber对象。
5:对于layercontents属性,使用CGImageRef属性类型。
6:对于CGPoint数据类型,可以使用NSValue对象进行包装,也可以使用CGPathRef对象使用路径进行包装。

CGPath

这是一个可选的路径对象,默认是nil。它定义了动画的行为,当path的值非nil时,将覆盖values属性的值,作用与values属性一样(即:如果你设置了path,那么values将被忽略。)对于常速路径动画, calculationMode应该被设置为 paced。我们可以设置一个CGPathRef\CGMutablePathRef,让层按照这个路径进行动画移动。

  • 关键帧时间(Keyframe timing)
  /* An optional array of `NSNumber' objects defining the pacing of the
     * animation. Each time corresponds to one value in the `values' array,
     * and defines when the value should be used in the animation function.
     * Each value in the array is a floating point number in the range
     * [0,1]. */
    open var keyTimes: [NSNumber]?

    /* An optional array of CAMediaTimingFunction objects. If the `values' array
     * defines n keyframes, there should be n-1 objects in the
     * `timingFunctions' array. Each function describes the pacing of one
     * keyframe to keyframe segment. */
    open var timingFunctions: [CAMediaTimingFunction]?

    /* The "calculation mode". Possible values are `discrete', `linear',
     * `paced', `cubic' and `cubicPaced'. Defaults to `linear'. When set to
     * `paced' or `cubicPaced' the `keyTimes' and `timingFunctions'
     * properties of the animation are ignored and calculated implicitly. */
    open var calculationMode: CAAnimationCalculationMode

keyTimes

这是一个可选的轨迹动画的时间数组,数组中的每一个值都是NSNumber对象,并且取值范围在 [0,1]。它定义了动画的步调,数组中的每一个值都与 values中的值一一对应(可以理解为对应的关键帧指定对应的时间点,keyTimes中的每一个时间值都对应values中的每一帧.)。

当keyTimes没有设置的时候,各个关键帧的时间是平分的。默认情况下,一帧动画的播放,分割的时间是动画的总时间除以帧数减去一。

可以通过下面的公式决定每帧动画的时间:总时间/(总帧数-1)。例如,如果你指定了一个5帧,10秒的动画,那么每帧的时间就是2.5秒钟:10/(5-1)=2.5。你可以做更多的控制通过使用 keyTimes 关键字,你可以给每帧动画指定总时间之内的某个时间点。

例子: [0,0.2,0.5,1];这里面设置的三个的动画时间,假设总时间是10秒,第一段的时间为2秒(0.2 - 0)x10,第二段的时间为3秒(0.5-0.2)x10,第三段的时间为5秒(1-0.5)x10。

timingFunctions

这是一个可选数组,数组中的值是CAMediaTimingFunction类型,如果values数组定义了n关键帧,那么该数组就需要 n-1个CAMediaTimingFunction值。每一个CAMediaTimingFunction值描述了关键帧从一个值到另一个值之间过渡的步调(即:运动的时间函数),具体值参考文章最开始部分。

calculationMode 计算动画的时间

该属性是关键帧动画中还有一个非常重要的参数,所谓计算模式:其主要针对的是每一帧的内容为一个坐标点的情况,也就是对anchorPoint和 position进行的动画。当在平面座标系中有多个离散的点的时候,可以是离散的,也可以直线相连后进行插值计算,也可以使用圆滑的曲线将他们相连后进行插值计算

calculationMode目前提供如下几种模式:

  • kCAAnimationLinear默认值,表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算,该模式提供了最大化控制动画的时间

  • kCAAnimationDiscrete离散的,不进行插值计算,所有关键帧直接逐个进行显示,该模式使用keyTimess属性,但是忽略timingFunctions属性。

  • kCAAnimationPaced使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions无效

  • kCAAnimationCubic 对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,这里的主要目的是使得运行的轨迹变得圆滑

  • kCAAnimationCubicPaced看名字就知道和kCAAnimationCubic有一定联系,其实就是在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的

注意:如果你想自己处理动画的时间,那么可以使用kCAAnimationLinear or kCAAnimationCubic 模式和keyTimes、timingFunctions属性。keyTimes将定义每一帧所对应的时间,每帧时间的中间值由timingFunctions设置对应的值进行控制。如果没有设置,将使用默认值。

  • 旋转模式属性(Rotation Mode Attribute)
   /* Defines whether or objects animating along paths rotate to match the
     * path tangent. Possible values are `auto' and `autoReverse'. Defaults
     * to nil. The effect of setting this property to a non-nil value when
     * no path object is supplied is undefined. `autoReverse' rotates to
     * match the tangent plus 180 degrees. */
    open var rotationMode: CAAnimationRotationMode?

定义是否沿着路径旋转匹配对象动画路径切线,值可能为kCAAnimationRotateAuto和kCAAnimationRotateAutoReverse.默认是nil.如果没有路径对象,设置该属性值将无效,kCAAnimationRotateAutoReverse为了匹配正切将添加180°.

  • 立方模式属性(Cubic Mode Attributes)
/* For animations with the cubic calculation modes, these properties
     * provide control over the interpolation scheme. Each keyframe may
     * have a tension, continuity and bias value associated with it, each
     * in the range [-1, 1] (this defines a Kochanek-Bartels spline, see
     * http://en.wikipedia.org/wiki/Kochanek-Bartels_spline).
     *
     * The tension value controls the "tightness" of the curve (positive
     * values are tighter, negative values are rounder). The continuity
     * value controls how segments are joined (positive values give sharp
     * corners, negative values give inverted corners). The bias value
     * defines where the curve occurs (positive values move the curve before
     * the control point, negative values move it after the control point).
     *
     * The first value in each array defines the behavior of the tangent to
     * the first control point, the second value controls the second
     * point's tangents, and so on. Any unspecified values default to zero
     * (giving a Catmull-Rom spline if all are unspecified). */
    
    // 该值控制着曲线的紧密度(正值将越紧,负值将越宽松)
    open var tensionValues: [NSNumber]?
   // 该值控制片段之间的链接(正值将有锋利的圆角,负值将是倒立的圆角)
    open var continuityValues: [NSNumber]?
    // 该值定义了曲线发生的地点(正值将在在控制点前移动曲线,负值将在控制点后移动)
    open var biasValues: [NSNumber]?

实战

背景颜色动画

func colorAnimation() {
     let animation = CAKeyframeAnimation(keyPath: "backgroundColor")
     animation.values = [UIColor.red.cgColor,
                         UIColor.green.cgColor,
                         UIColor.blue.cgColor]
     animation.keyTimes = [0, 0.5, 1]
     animation.duration = 2
     animation.repeatCount = 3
     layer.add(animation, forKey: nil)
  }

效果如下

2018-11-23 17-00-19.2018-11-23 17_00_44.gif

晃动动画

 func shakeAnimation() {
      let animation = CAKeyframeAnimation()
      animation.keyPath = "position.x"
      animation.values = [0, 10, -10, 10, -5, 5, -5, 0 ]
      animation.keyTimes = [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1]
      animation.duration = 1
      animation.isAdditive = true
      layer.add(animation, forKey: "shake")
 }

效果如下

2018-11-23 17-05-01.2018-11-23 17_05_11.gif

路径动画

沿椭圆轨迹进行动画

 func pathAnimation() {
     let animation = CAKeyframeAnimation(keyPath: "position")
     animation.duration = 2
     // 设置动画轨迹,可以根据需求绘制自己需要的动画轨迹
     animation.path = UIBezierPath(ovalIn: CGRect(x: 50, y: 150, width: 300, height: 300)).cgPath
     animation.repeatCount = Float.infinity
     layer.add(animation, forKey: nil)
 }

效果如下

2018-11-23 17-16-07.2018-11-23 17_16_27.gif

CAAnimationGroup

CAAnimationGroup是个动画组,可以同时进行缩放,旋转(同时进行多个动画)。默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间。

  open class CAAnimationGroup : CAAnimation {
        /* An array of CAAnimation objects. Each member of the array will run
         * concurrently in the time space of the parent animation using the
         * normal rules. */
        open var animations: [CAAnimation]?
  }

实战

透明度和背景色的组合效果
  func groupAnimation() {
        // 透明度
        let fadeOut = CABasicAnimation(keyPath: "opacity")
        fadeOut.fromValue = 1
        fadeOut.toValue = 0.1
        fadeOut.duration = 1

        // 缩放
        let expandScale = CABasicAnimation()
        expandScale.keyPath = "transform"
        expandScale.valueFunction = CAValueFunction(name: kCAValueFunctionScale)
        expandScale.fromValue = [1, 1, 1]
        expandScale.toValue = [3, 3, 3]

        // 组合动画
        let fadeAndScale = CAAnimationGroup()
        fadeAndScale.animations = [fadeOut, expandScale]
        fadeAndScale.duration = 1
        fadeAndScale.repeatCount = 5
        fadeAndScale.autoreverses = true
        layer.add(fadeAndScale, forKey: nil)
  }

效果如下

2018-11-23 17-37-40.2018-11-23 17_37_57.gif

注意

The delegate and isRemovedOnCompletion properties of animations in the animations array are currently ignored. The CAAnimationGroup delegate does receive these messages.

CATransition

CATransitions是CAAnimation的子类,CATransition类为layer实现了过渡动画,我们可以从一系列预定义的过渡动画中指定过渡效果或者提供自定义的CIFilter实例。

open class CATransition : CAAnimation {
    open var type: CATransitionType //动画过渡类型
    open var subtype: CATransitionSubtype? //动画过渡类型
    open var startProgress: Float //动画起点(在整体动画的百分比)
    open var endProgress: Float //动画起点(在整体动画的百分比)
}

type

extension CATransitionType {
    // 交叉淡化过渡
    public static let fade: CATransitionType
    // 交叉淡化过渡
    public static let moveIn: CATransitionType
    // 新视图把旧视图推出去
    public static let push: CATransitionType
    //  将旧视图移开,显示下面的新视图
    public static let reveal: CATransitionType
}

subtype

extension CATransitionSubtype {
    // 指定动画方向,从右到左
    public static let fromRight: CATransitionSubtype
    // 指定动画方向,从左向右
    public static let fromLeft: CATransitionSubtype
    // 指定动画方向,从左向右
    public static let fromTop: CATransitionSubtype
    // 指定动画方向,从下到上
    public static let fromBottom: CATransitionSubtype
}

CATransition通常用于通过CALayer控制UIView内子控件的过渡动画,比如,删除子控件,添加子控件,切换两个子控件等.具体使用步骤总结:

  • 1:创建CATransition对象.
  • 2:为CATransition设置typesubtype两个属性,其中 type 指定动画的类型,subtype指定动画移动的方向.
  • 3:如果不需要动画执行的整个过程(就是只要动画执行到中间部分就停止),可以指定startProgress(动画开始的进度),endProgress(动画结束的进度)属性.
  • 4:调用layer的 init(keyPath path: String?)即可进行动画。第一个参数为CAAnimation对象,第二个参数为用于该动画对象执行的唯一标识。

实战

写一个简单的demo,点击屏幕切换视图

class CATrainsitionViewController: UIViewController {

    lazy var cyanView: UIView = {
        let view = UIView(frame: self.view.bounds)
        view.backgroundColor = UIColor.cyan
        return view
    }()
    lazy var redView: UIView = {
        let view = UIView(frame: self.view.bounds)
        view.backgroundColor = UIColor.red
        return view
    }()
     var count = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.white
        view.addSubview(cyanView)
        view.addSubview(redView)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let toView = count % 2 == 0 ? cyanView: redView
        count += 1
        // animation here...

        view.bringSubview(toFront: toView)
    }
}

moveIn

 let transition = CATransition()
 transition.duration = 2.0
 transition.type = kCATransitionMoveIn
 transition.subtype = kCATransitionFromRight
 view.layer.add(transition, forKey: nil)
2018-11-23 19-03-46.2018-11-23 19_04_09.gif

下面几种效果修改type即可,subtype根据需求设置方向

fade

transition.type = kCATransitionFade
transition.subtype = kCATransitionFromTop
2018-11-23 19-10-21.2018-11-23 19_10_37.gif

reveal

transition.type = kCATransitionReveal
transition.subtype = kCATransitionFromTop
2018-11-23 19-13-23.2018-11-23 19_13_35.gif

push

 transition.type = kCATransitionPush
 transition.subtype = kCATransitionFromLeft
2018-11-23 19-07-27.2018-11-23 19_08_07.gif

CASpringAnimation

CASpringAnimation iOS9才引入的动画类,它继承于CABaseAnimation,用于制作弹簧动画效果。

/** Subclass for mass-spring animations. */
@available(iOS 9.0, *)
open class CASpringAnimation : CABasicAnimation {
    // 质量,影响图层运动时的弹簧惯性,质量越大,弹簧拉伸和压缩的幅度越大,动画的速度变慢.默认值为1
    open var mass: CGFloat

    // 刚度系数(劲度系数/弹性系数),刚度系数越大,形变产生的力就越大,运动越快。默认值为100
    open var stiffness: CGFloat

    // 阻尼系数,阻止弹簧伸缩的系数,阻尼系数越大,停止越快。默认值为10 
    open var damping: CGFloat

    // 初始速率,动画视图的初始速度大小,速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反
    open var initialVelocity: CGFloat

   // 结算时间返回弹簧动画到停止时的估算时间,根据当前的动画参数估算,通常弹簧动画的时间使用结算时间比较准确
    open var settlingDuration: CFTimeInterval { get }
}

实战

实现触摸屏幕,使红色小方块移动到对应的点,并且来回上下弹性运动

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touch = touches.first!
    let touchPoint = touch.location(in: self.view)

    let animation = CASpringAnimation(keyPath: "position")
    animation.fromValue = NSValue(cgPoint: layer.position)
    animation.toValue = NSValue(cgPoint: touchPoint)
    animation.damping = 5  //阻尼系数越大,停止越快
    animation.stiffness = 100 //刚度系数越大,形变产生的力就越大,运动越快
    animation.mass = 1 //速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反
    animation.duration = 2 //质量越大,弹簧拉伸和压缩的幅度越大
    layer.add(animation, forKey: nil)
    layer.position = touchPoint
 }

效果如下

2018-11-24 08-00-22.2018-11-24 08_00_49.gif

优秀的第三方库

YapAnimator
EasyAnimation
POP

参考

Core Animation Guide

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容