显式动画

隐式动画是在iOS平台创建动态用户界面的一种直接方式,也是UIKit动画机制的基础,不过它并不能涵盖所有的动画类型。本文旨在要研究显式动画,它能够对一些属性做指定的自定义动画,或者创建非线性动画,比如沿着任意一条曲线移动。

CAAnimation简介

研究显式动画的关键是分析CAAnimation及其子类,CAAnimation和其子类的继承体系如下:

|--CAAnimation: CAMediaTiming
|--CAPropertyAnimation
|--CABasicAnimation
|--CAKeyframeAnimation
|--CATransition
|--CAAnimationGroup

一般情况下不直接使用CAAnimation,而使用其子类;并且CAPropertyAnimation也不能直接使用。

CAAnimation遵循CAMediaTiming协议,常用属性包括:

  • beginTime – 控制动画开始时间,譬如CACurrentMediaTime()+1.0对应的是1秒的delay;
  • duration – 控制动画的持续时间;
  • repeatCount – 动画的重复次数,永久重复对应HUGE_VALF1表不重复;
  • timingFunction – 控制动画运行的节奏,一般使用CAMediaTimingFunction(name:)赋值,name可设置为:
    • kCAMediaTimingFunctionLinear
    • kCAMediaTimingFunctionEaseIn
    • kCAMediaTimingFunctionEaseOut
    • kCAMediaTimingFunctionEaseInEaseOut
    • kCAMediaTimingFunctionDefault
  • delegate – 动画代理,用来监听动画的执行过程,两个方法:
    • func animationDidStart(animation: CAAnimation)
    • func animationDidStop(animation: CAAnimation, finished flag: Bool)
  • removedOnCompletion – 当设置为true时,动画完成后,layer又会回到原处;当设置为false,且fillMode设置为kCAFillModeForwards时,layer动画完成后不动;
  • fillMode – determine how the timed object behaves once its active duration has completed.

额外阐述一下fillMode属性。在此之前先引入几个概念,经历一个完整动画的layer的时间线可分为3个部分:

  • 动画前 – 添加动画到动画开始前的时间段;
  • 动画中 – 动画开始到结束的时间段;
  • 动画后 – 动画结束后的时间;

动画的实质是layer的属性值从某个值到另一个值的变化过程,站在属性值的角度同样有3个概念:

  • origin value – 添加动画前的属性值
  • from value – 动画的初始值
  • to value – 动画的结束值

fillMode的属性值会影响layer在动画前动画后的行为,有4种可设置的属性值:

  • kCAFillModeRemoved – 此为默认值,layer在动画前的属性值一直未origin value,动画开始时属性值立马切换到from value,然后动画变化到to value,动画结束时属性值又立马切换到origin value;
  • kCAFillModeForwards – 相对于kCAFillModeRemoved,layer在动画结束时仍然保持属性值为to value;
  • kCAFillModeBackwards – 相对于kCAFillModeRemoved,layer在动画前就将属性值切换到origin value;
  • kCAFillModeBothkCAFillModeForwardskCAFillModeBackwards的合体;

Note: kCAFillModeForwards起作用的前提是removedOnCompletion的属性值被设置为falsekCAFillModeBackwards的作用在delay不为0时才能直观感受到,更多内容参考CAMediaTiming – Fill Modes

使用CAAnimation的基本套路

使用CAAnimation(或者其子类)的基本套路是:

  1. 初始化一个CAAnimation对象,并设置一些动画相关属性;
  2. 调用CALayer#addAnimation(_:forKey:)方法添加CAAnimation对象到CALayer中,这样就能开始执行动画了;
  3. 调用CALayer#removeAnimationForKey(_:)方法停止动画;

CALayer#addAnimation(_:forKey:)里的参数forkey没有太多别的要求,可以是任意字符串,甚至可以是nil

CAPropertyAnimation

CAPropertyAnimation继承CAAnimation,额外定义了一些属性:

  • keyPath – Specifies animated property. 譬如"frame""position"等;
  • additive – Determines if the value of the property is the value at the end of the previous repeat cycle, plus the value of the current repeat cycle. 不太懂!
  • cumulative – Determines if the value specified by the animation is added to the current render tree value to produce the new render tree value. 不太懂!
  • valueFunction – An optional value function that is applied to interpolated values. 很少会用到!

Note: CAPropertyAnimation里的keyPathCALayer#addAnimation(_:forKey:)里的key不一样,前者只能是"frame""position"之类的对应CALayer属性的字符串,后者可以是任意字符串,可以认为是给animation贴个ID标签。

CABasicAnimation

CABasicAnimation在继承CAPropertyAnimation的基础上又定义了三个属性:

@interface CABasicAnimation : CAPropertyAnimation
@property(nullable, strong) id fromValue;
@property(nullable, strong) id toValue;
@property(nullable, strong) id byValue;
@end

CAAnimation.h里介绍了几种使用模式,一般来说,使用fromValue/toValuefromValue/byValue组合即可,或者直接使用byValuetoValue

CAKeyframeAnimation

Key frame animation常被翻译为:关键帧动画。顾名思义,CAKeyframeAnimation可以实现让animated properties按照一串数值进行动画,就好像逐帧制作动画一样。

P.S: 不要被CAKeyframeAnimation中的frame所迷惑,它和CALayer#frame中的frame不是一回事儿。

相较而言,CABasicAnimation处理动画更粗糙一些,只是单纯地让layer的属性值从a到b;考虑到CAKeyframeAnimation的复杂性,这一部分对它的阐述会更详细一些。

Providing Keyframe Values

动画处理的本质是改变属性值,CABasicAnimation提供了fromValuebyValuetoValue来「装载」待处理的属性值。

CAKeyframeAnimation处理动画更精细一些,动画过程中所需要指定的属性值自然更多(一般来说),因此提供了数组类型属性values来「装载」待处理的属性值。

举个例子,现在沿正方形路径移动一个view,所经过的路径是(100, 100)->(200, 100)->(200, 200)->(100, 200)->(100, 100),代码如下:

let kfa = CAKeyframeAnimation(keyPath: "position")
kfa.values = [NSValue(CGPoint: CGPoint(x: 100, y: 100)), // 左上角
NSValue(CGPoint: CGPoint(x: 200, y: 100)), // 右上角
NSValue(CGPoint: CGPoint(x: 200, y: 200)), // 右下角
NSValue(CGPoint: CGPoint(x: 100, y: 200)), // 左下角
NSValue(CGPoint: CGPoint(x: 100, y: 100))] // 左上角
kfa.duration = 4.0
kfa.removedOnCompletion = false
kfa.fillMode = kCAFillModeBoth
animationView.layer.addAnimation(kfa, forKey: nil)

除了valuesCAKeyframeAnimation还提供了path属性,当使用CAKeyframeAnimation构建point-based的动画时,使用path是个不错的选择。

P.S: 显然,valuespath只需要使用一个即可,然而,当同时赋予valuespath以有效值,哪个会起作用呢?验证发现,当给path设置了有效值后,values的值就不会起作用。

Keyframe Timing

values将一个完整的动画分为n个关键帧(假设数组长度为n),默认情况下,相邻帧之间的时间间隔为:duration/(n-1)。

但是CAKeyframeAnimation提供了丰富的接口以便进行更精密更丰富的自定义,相关属性包括:keyTimescalculationMode以及timingFunctions

keyTimes也是数组类型,简单来说,keyTimes中的元素将动画时间切割成几部分,譬如[0, 0.5, 1.0]将时间切割为两部分:0-0.5duration、0.5duration-1.0*duration。

Note: 一般来说,keyTimes的元素个数和values的元素个数一致,或者与path的点的个数一致,否则,动画会出现想象不到的结果;但有特殊情况,下文会提到。

calculationMode指示了关键帧之间的值如何计算,可选的值包括:

  • kCAAnimationLinear,默认值,线性过渡;
  • kCAAnimationDiscrete,从一个关键帧跳到下一个关键帧,直接跳跃,无过渡;
  • kCAAnimationPaced
  • kCAAnimationCubic
  • kCAAnimationCubicPaced

P.S: kCAAnimationLinearkCAAnimationDiscrete相对比较容易理解,后3种没怎么用到过,理解不深刻,希望以后能够补充。

第三个属性timingFunctions和上文提到的timingFunction类似,只是这里是一个数组类型,它的每个元素控制的是两个相邻关键帧之间timing curve。

Note: 若属性values中指定的关键帧个数为n(元素个数),则timingFunctions的元素个数应该为n-1

再着重介绍keyTimes配合calculationMode使用的注意事项:

  • 如果calculationMode的值为kCAAnimationLinearkCAAnimationCubickeyTimes的第一个元素值必须为0.0,最后一个元素值必须为1.0,其他的元素值(如果有)范围在0.0-1.0之间;
  • 如果calculationMode的值为kCAAnimationDiscretekeyTimes的第一个元素值必须为0.0,最后一个元素值必须为1.0;此外,若value元素个数为n,则keyTimes的元素数量应该为n+1
  • 如果calculationMode的值为kCAAnimationPacedkCAAnimationCubicPacedkeyTimes的值会被忽略。

Rotation Mode Attribute

rotationMode, it determines whether objects animating along the path rotate to match the path tangent.

该属性配合path使用,有三个可选值:nilkCAAnimationRotateAuto以及kCAAnimationRotateAutoReverse。设置一段曲线,配置相应的属性值,能较直观的感受到它们的作用。

Cubic Mode Attributes

还有三个不常用到的属性:tensionValuescontinuityValuesbiasValues

它们仅在calculationMode属性值为kCAAnimationCubickCAAnimationCubicPaced时起作用。还没详细体会过,暂时略过。

关于CAKeyframeAnimation的更多内容参考CAKeyframeAnimation Class Reference

CATransition

暂时略过,详细内容参考CATransition Class Reference

iOS开发之各种动画各种页面切面效果中还介绍了更多,只是使用这些私有资源会存在被Apple Store打回的风险。

P.S: 曾以为CATransition在转场动画中会用得比较多,但事实上,似乎用得并不多。

CAAnimationGroup

每个CAAnimation只能处理一个animated property,但有时候需要同时处理多个animated properties,此时就会用到CAAnimationGroup。其定义非常简单:

public class CAAnimationGroup : CAAnimation {
public var animations: [CAAnimation]?
}

其用法就不消多说了,easy。

本文参考