iOS 动画详解,官方文档参考

这篇文章主要是参考了官方文档 Core Animation Programming Guide
,加上自己个人的demo整理出来自用的,也贴出来供大家一起参考。

概览

Core Animation属于QuartzCore框架中的一部分,( = Core Animation + CALayer类及其派生类 + 少部分其它类与协议),其中:
CALayer = 渲染模型,持有渲染的几何图层的原始数据。
Core Animation = 动画控制类,实时更改渲染模型的属性值,并提交渲染,每帧连贯起来呈现动画。

介绍

Core Animation包括:


Core Animation的类与协议

#1 - CAMediaTiming协议
定义了一套抽象接口

#2 - CAAnimation
抽象父类,CAMediaTiming协议的适配器,为协议方法提供了默认实现

#3 - CAAnimationGroup
动画组类,引用着一个数组,数组内的多个动画对象将会按组的动画设定,同时并发执行

#4 - CAPropertyAnimation
属性动画抽象类,根据keyPath(CALayer的属性)作动画

#5 - CABasicAnimation
基础动画类,根据两个属性值(keyPath的formValue和toValue)作最基础简单的补全动画

#6 - CAKeyframeAnimation
帧动画类,可看作顺序执行的多个CABasicAnimation的合成版动画,并且可以定制动画的运动路径

#7 - CASpringAnimation
弹簧动画类,模拟弹簧物件的物理运动来改变属性值的动画

#8 - CATransition
转场动画类,提供对渲染内容(CALayer的content)改变的平滑过渡效果

除去协议与伪抽象类,故开发时直接可以使用的子类有:
CABasicAnimation
CAKeyframeAnimation
CASpringAnimation(iOS 9.0+)
CAAnimationGroup
CATransition

详细拆解

#1 - CAMediaTiming协议

一套抽象接口。描述了动画的与时间、执行模式相关的信息。
CALayer和CAAnimation都实现了该协议。

beginTime

该协议中最难理解的一个属性。在Core Animation中,对于一个CALayer或CAAnimation,都有一条自己的时间线timeline。而.beginTime是在父object的时间线中,layer开始渲染或animation动画开始渲染的时间点(本地时间)。
比如:
layer.beginTime是layer在layer.superlayer的时间线中,layer开始渲染的时间点;
animation.beginTime是animation在[animLayer addAnimation:animation forKey:nil]的那个animLayer的时间线中,animation开始动画的时间点。

除此之外,和beginTime相关的,Core Animation还有一个absoluteTime(绝对时间),通过CACurrentMediaTime()获取,其本质为系统时钟时间mach_absolute_time()的秒数值。

下面通过一个例子来说明。
现象:superlayer在屏幕显示之后,layer延迟了2.0秒才显示。

CALayer *superlayer = [CALayer layer];
..
CALayer *layer = [CALayer layer];
layer.beginTime = [layer convertTime:CACurrentMediaTime() fromLayer:superlayer]+2.0f;
[superlayer addSublayer:layer];

解释:
由于layer.beginTime属性设置的是layer在superlayer的时间线中开始渲染的时间点(本地时间),
而CACurrentMediaTime()可以理解为在现实世界随时间流逝的现在这个时刻,
convertTime: fromLayer:将现在这个时刻(绝对时间)转换成相应的这个时刻在super的时间线中对应的时间点,
然后对应的时间点上加上了2.0秒,并设置为layer的beginTime。
所以最后的结果是看到的是layer在现实世界2.0秒后延迟出现在屏幕上。

duration

动画的基础时长(单次时长)。默认为0。

speed

动画的速率(快进慢放)。默认为1,即保持为原动画速率。

timeoffset

动画的时间偏移。比方说将一个有效时长duration=10.0f的动画,看作是播放10秒的视频。timeoffset=6.0f,那么将会从6秒处开始播放。接下来,要走完10秒的有效播放时长,因此播了4秒后到达从10秒结束处,又跳回timeoffset=0即0秒处继续播放剩下的6秒。即播放路径:
6.0秒处->10.0秒处->0.0秒处->6.0秒处;

repeatCount

动画重复播放次数。默认为0。

repeatDuration

重复进行动画的有效时长。当repeatCount设定为!=0时,
1、当repeatDuration==0,动画的有效时长等于repeatCount*duration
2、当repeatDuration!=0,动画的有效时长等于repeatDuration,忽略duration

autoreverses

单次动画结束时,再反向进行动画。

fillMode

动画的填充模式,或者说定格行为。这个属性也是容易让人混淆的,下面有个例子,起始layer在屏幕居中的位置,动画设定为1秒延迟后,3秒内从屏幕的最左边缓慢平移到屏幕的最右边。

anim.beginTime = 1.0f;// 伪代码,表示延迟1秒执行。
anim.duration = 3.0f;
anim.fillMode = ..;
[layer addAnimation:anim forKey:nil];

kCAFillModeRemoved:fillMode的默认值。动画开始之前,layer保持屏幕在居中位置。动画在开始时,layer突闪到最左的位置,然后缓慢平移动最右的位置。动画结束后layer突闪回居中位置
kCAFillModeForwards:动画结束时,layer渲染到动画的结束处(屏幕的最右边,不突闪回居中位置了)(**需要同时设置anim.removedOnComplection=NO)
kCAFillModeBackwards:动画开始之前,一开始就把layer渲染在动画的开始处(屏幕的最左边,就是说,动画开始之前layer已经在动画开始处就绪,不发生突闪)
kCAFillModeBoth:以上两个模式的组合

#2 - CAAnimation

实现适配了CAMediaTiming协议,内部封装底层渲染API,在Core Animation中作为抽象父类。为使用Core Animation的功能,开发中所有动画类都应该继承于它。

属性/方法 说明
+ (instancetype)animation 创建一个动画对象
+ (nullable id)defaultValueForKey:(NSString *)key 根据属性名返回它的默认值。比如fillmode。主要用于继承时重写,给出动画属性默认值
- (BOOL)shouldArchiveValueForKey:(NSString *)key 返回key对于的属性值在调用encodeWithCoder:时是否可以归档
timingFunction 时间函数
delegate 代理对象
removedOnComplection 动画结束时(从渲染树)移除
delegate

id类型对象。根据动画不同的的生命周期,会回调代理对象的以下两个方法

方法 说明
- (void)animationDidStart:(CAAnimation *)anim 动画开始时
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag 动画结束时,flag为YES代表动画走完了有效时长,NO表示动画提前被移除

NSObject默认实现了CAAnimationDelegate分类,而所有类继承自NSObject,因此为动画设置delegate时,只需要重写以上两个方法即可。

timingFunction

CAMediaTimingFunction时间函数类对象。用于描述动画的执行速率(三次贝塞尔曲线)。
从CAMediaTimingFunction类获取对象有两种方式:

  • 一是使用+functionWithName:,根据function_name获取常用的预配置函数,目前系统提供的有四种:



    (执行加速度的比较可以看函数线上某点的切线斜率)
    kCAMediaTimingFunctionLinear:匀速执行
    kCAMediaTimingFunctionEaseIn:从开始缓慢执行,突然加速(淡入快出)
    kCAMediaTimingFunctionEaseOut:从快速执行,直到快结束时突然减速(快入淡出)
    kCAMediaTimingFunctionEaseInEaseOut:开始和结束都是缓慢执行,中间过程加速执行(淡入淡出)

  • 二是通过传入两个点坐标(贝塞尔曲线控制点坐标。三次贝塞尔曲线需要4个控制点确定,系统默认添加了(0,0)和(1,1)两个控制点,因此只需传入两个控制点坐标)获取自定义的函数:
    (c1&c2点坐标)
    + (instancetype)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;
    - (instancetype)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;
    至于如何算控制点坐标,和相应的贝塞尔曲线,这里给个传送门,手动拖一拖就行,很直观。
补充一下CAMediaTimingFunction类的方法:
方法 说明
+ (instancetype)functionWithName:(NSString *)name 根据name获取系统预配置函数
+ (instancetype)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y; 根据c1点和c2点坐标获取相应的自定义贝塞尔曲线函数
- (instancetype)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y 同上
- (void)getControlPointAtIndex:(size_t)idx values:(float[2])ptr 获取指定idx控制点的坐标(共4个控制点,因此idx取值为0<=idx<=3)
#3 - CAPropertyAnimation

继承自CAAnimation,作为抽象动画类,其意义为动画是根据某项属性(keyPath)渲染的。由于动画依赖于CALayer,那么这个keyPath当然就是layer相应的keyPath了,比如position.y等。

属性/方法 说明
+ (instancetype)animationWithKeyPath:(nullable NSString *)path 返回动画对象并设置keyPath
keyPath 将根据此项属性值渲染动画,keyPath的取值在下面会列举
additive 若为YES,把当前呈现layer相应keyPath的value,与动画设置的值value(它的子类中会提供),两者进行叠加作为该动画最终渲染value<br />(下面会举例说明)
cumulative 和上面类似,但只对于重复执行的动画,下一轮的动画的初始value为动画设置的value叠加上一轮的结束时呈现的value<br />(下面会举例说明)
valueFunction 对keyPath为transform是如何插值计算的(tranform3D的Rotate,Scale,Translate)<br />(下面会举例说明)
keyPath支持的属性有:

几何属性:
bounds // 大小
position // 位置
frame // **根据bounds和position计算出来的属性,不支持动画
anchorPoint // 几何锚点。详情看CALayer篇
cornerRadius // 圆角半径
transform // 几何变换
zPosition // zPosition值越大,在父layer中的视图层级越高。zPosition=1的layerA将覆盖在zPosition=0的layerB之上。

背影属性:
backgroundColor // 背景颜色渐变

渲染内容属性:
contents // 内容渐变,常设:CGImage
contentsRect // 矩形范围内容才会被渲染,并拉伸到layer大小
masksToBounds // 截掉超出layer的内容

子Layer:
sublayers // 所有的子layer
sublayerTransform // 所有子layer的几何变换

边框:
borderColor // 边框颜色
borderWidth // 边框宽度

阴影:
shadowColor // 阴影颜色
shadowOffset // 阴影偏移
shadowOpacity // 阴影不透明度
shadowRadius // 阴影半径
shadowPath // 阴影路径

不透明度:
opacity

遮罩:
mask

其它:
doubleSided // layer是否显现双面。**无隐式动画
hidden // 隐藏

CAPropertyAnimation其它属性举例:

(例子会用到CABasicAnimation,继承自CAPropertyAnimation,其中fromValue和toValue指的是动画从keyPath的fromValue开始,到toValue结束。比如keyPath=@"position.y",fromValue=0,toValue=100表示layer从position的y=0运动到y=100。)

layer的初始状态为

CALayer *layer = [CALayer layer];
layer.bounds = CGRectMake(0,0,60,60);// 大小
layer.position = CGPointMake(100,100);// 位置
additive:
CABasicAnimation *anim = [CABasicAnimation animation];
anim.duration = 3;
anim.keyPath = @"position.y";// 动画类型为y轴坐标值变化动画
anim.fromValue = @100;// 从
anim.toValue = @200;// 到

anim.additive = NO;// layer将从poistion.y=100运动到200

// 呈现layer相应keyPath的值,即layer的poistion的y为100(layerPosY=100)
anim.additive = YES;// 若为YES,fromValue+=layerPosY,toValue+=layerPosY
                    // 即100+@100开始运动到100+@200
[layer addAnimation:anim forKey:nil];
cumulative:
anim.repeatCount = 2;// 动画重复执行2次
anim.cumulative = NO;// layer从y=100运动到200结束,layer重新从y=100开始运动(lastPosY=200)

anim.cumulative = YES;// 将进行第2轮动画时,fromValue+=lastPosY,toValue+=lastPosY
                      // 即从200+@100运动到200+@200
valueFunction:
CABasicAnimation *anim2 = [CABasicAnimation animation];
anim2.keyPath = @"transform";
anim2.fromValue = @0;
anim2.toValue = @M_PI;

anim2.valueFunction = [CAValueFunction functionWithName:kCAValueFunctionRotateY];// 为RotateY进行插值

也可以不设置valueFunction,
keyPath改为@"transform.rotation.y"
或者.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI, 0, 1, 0)];
都是一样效果:


绕y轴旋转180度

此外,CAValueFunction的+functionWithName:预设有:
kCAValueFunctionRotateX // 绕x轴旋转
kCAValueFunctionRotateY // 绕y轴旋转
kCAValueFunctionRotateZ // 绕z轴旋转
kCAValueFunctionScale // 大小缩放。应为value赋值为数组@[@x, @y, @z],表示x,y,z轴上的缩放比例
kCAValueFunctionScaleX // x轴上缩放
kCAValueFunctionScaleY // y轴上缩放
kCAValueFunctionScaleZ // z轴上绽放
kCAValueFunctionTranslate // 位移,类似kCAValueFunctionScale
kCAValueFunctionTranslateX // x轴位移
kCAValueFunctionTranslateY // y轴位移
kCAValueFunctionTranslateZ // z轴位移

#4 - CABasicAnimation

继承自CAPropertyAnimation,该子类只有三个自己的属性,类如其名基础易上手,是使用频率非常高的一个动画类。

CABasicAnimation *anim = [CABasicAnnimation animation];
anim.keyPath = @"opacity";// 指明value对应的是哪个属性的value
anim.fromValue = 0;
anim.toValue = 1;

表示opacity不透明度从0到1的动画。

属性 说明
fromValue 动画(keyPath)开始值
toValue 动画(keyPath)结束值
byValue 动画有效时长内的(keyPath)增量

举个byValue的例子:

layer.opacity = 0.1;

CABasicAnimation *anim = [CABasicAnnimation animation];
anim.keyPath = @"opacity";
anim.byValue = 0.6;
anim.duration = 3;

表示在动画在3秒内,layer的opacity不透明度增加了0.6。即在动画最后,layer的opacity为0.1+0.6=0.7。
byValue可为负。

#5 - CAKeyframeAnimation

继承自CAPropertyAnimation。关键帧补间插值动画,向其提供多个(keyPath)的value作为关键帧,关键帧之间自动进行补全插值,呈现连贯动画。同样继承自CAPropertyAnimation,与CABaiscAniamtion比较,CAKeyframeAnimation可看作是多段的CABaiscAniamtion的定制合成。

先来认识下它的属性,再结合例子理解。

属性 说明
values 数组类型,(keyPath)的多个关键帧值
path CGPath类型,仅当keyPath为anchorPoint或position时适用,point点将在设置的path路径上进行运动。<br />(**设置path会使values属性失效 )
keyTimes NSNumber数组类型,数组元素值取值范围从0.0f到1.0f。它表示某关键帧,或者设置了path时path某控制点的呈现时间点(**将乘以duration)。<br /> keyTimes个数应与values或path的控制点个数相对应。
timingFunctions CAMediaTimingFunction数组类型,对于多段的关键帧补间,应该设置该时间函数数组属性<br />(**而不是继承父类的那个timingFunction属性)<br />,timingFunctions个数应为keyTimes个数-1
calculationMode 补间插值模式。预设的有:<br />kCAAnimationLinear(默认):<br />分段线性,将分段的关键帧补间timingFunction默认设置为线性<br />kCAAnimationDiscrete:<br />离散模式,关键帧之间不补间,跳跃式呈现<br />kCAAnimationPaced:<br />全局线性,将使keyTimes与timeFunctions属性失效,所有关键帧的时间间隔相等,整个动画呈匀速进行<br />kCAAnimationCubic:<br />曲线圆滑模式,计算出过所有点(values或path控制点)的圆滑曲线,作为动画的补间插值函数(表现为分段之间平滑过渡)<br />kCAAnimationCubicPaced:<br />全局曲线圆滑模式,将keyTimes与timeFunctions属性忽略,再做与上面类似的计算
tensionValues 仅当calculationMode设为kCAAnimationCubic时适用。(kCAAnimationCubic与kCAAnimationCubicPaced模式计算的曲线是数学上称为Catmull-Rom spline的曲线,这个属性和下面两个属性是其数学公式的参数,用于定制这条曲线,取值都为-1到1,默认值都为0。)<br />越比0大,(曲线点与点之前的)圆弧越紧致;越比0小,圆弧越偏圆
continuityValues 越比0大,圆弧越尖(偏三角);负值为倒置的圆弧
biasValues 偏移率。越比0大,圆弧越比0时的圆弧向外偏移;越比0小,越向内陷<br />(**没有特殊需求的话,以上三个属性值都无须修改。单设置kCAAnimationCubic就可以获得系统预设的平滑过渡)
rotationMode 旋转模式,仅当设置了path属性时适用。在layer运动的过程中:<br />kCAAnimationRotateAuto:layer的x轴正方向与运动路径的切线方向相平行且相同<br />kCAAnimationRotateAutoReverse:layer在kCAAnimationRotateAuto基础上,再旋转180度

举例:

values和keyTimes
layer.position = (CGPoint){20, 350};

// 关键帧动画
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
anim.keyPath = @"position";// 动画类型为位置变化动画
anim.duration = 3.0f;

// keyTime=0.0f,即在keyTime*duration = 0.0秒时刻,layer呈现为position = v1的值
// keyTime=0.2f,即在0.6秒时刻,呈现为v2
// keyTime=1.0f,即在3.0秒时刻,呈现为v3
// 中间的空隙,自动进行补间插值
NSValue *v1 = [NSValue valueWithCGPoint:(CGPoint){20, 350}];
NSValue *v2 = [NSValue valueWithCGPoint:(CGPoint){150, 150}];
NSValue *v3 = [NSValue valueWithCGPoint:(CGPoint){280, 350}];
anim.values = @[v1, v2, v3];
anim.keyTimes = @[@0.0f, @0.2f, @1.0f];// 与values一一对应
path

上面的例子等价于:

// moveToPoint、addLineToPoint的point(控制点)相当于上面的value
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 30, 350);
CGPathAddLineToPoint(path, NULL, 160, 150);
CGPathAddLineToPoint(path, NULL, 290, 350);
anim.path = path;// path赋值时自动copy
CGPathRelease(path);// 手动释放内存

// 设置了path值,将会忽略values属性
// anim.values = nil;

[layer addAnimation:anim forKey:nil];
timingFunctions:
anim.keyTimes = @[@0.0f, @0.5f, @1.0f];// 把@0.2f改成@0.5f,更方便观察

// 不同补间插值分段,使用各自的时间函数
// v1到v2段时间函数,慢入快出
CAMediaTimingFunction *v1v2 = [CAMediaTimingFunction functionWithControlPoints:0.62f :0.12f :0.93f :0.17f];
// v2到v3段时间函数,快入慢出
CAMediaTimingFunction *v2v3 = [CAMediaTimingFunction functionWithControlPoints:0.81f :0.12f :0.25f :0.92f];
anim.timingFunctions = @[v1v2, v2v3];// 元素个数应当为keyTimes个数-1,默认为线性

注意,在前一张图的例子中,并没有设置timeFunctions,它默认为线性。即v1v2、v2v3虽然各段的动画时间不一样,但都是匀速进行的。

rotationMode:

当rotationMode设为nil(默认为nil)时,不发生旋转:

// anim.rotationMode = nil;

设为kCAAnimationRotateAuto,旋转至切线方向:


设为kCAAnimationRotateAutoReverse,切线方向再旋转180度:

calculationMode:

接下来换一个例子来理解calculationMode。
有一个3秒内y坐标值变化动画,并设置calculationMode,加到layer上

CAKeyframeAnimation *yAnim = [CAKeyframeAnimation animation];
yAnim.duration = 3.0f;

yAnim.keyPath = @"position.y";
yAnim.values = @[@350, @120, @200, @160, @350];
yAnim.calculationMode = kCAAnimationLinear;

由于此layer动画让layer单纯上下运动,比较难观察calculationMode是如何对动画产生影响的(使y坐标值的变化)
那么再给layer一个随时间变化x坐标匀速增加的辅助动画,同样是3秒

CABasicAnimation *xAnim = [CABasicAnimation animation];
xAnim.keyPath = @"position.x";
xAnim.duration = 3.0f;
xAnim.byValue = @290;

接下来跟踪layer的运动曲线,观察设置了calculationMode的yAnim的layer的y坐标值的变化

  • kCAAnimationLinear(默认):



    设为kCAAnimationLinear时,并且没有设置timeFunctions,那么默认各段都是匀速补间插值的,可以看出单位时间内y值均匀变化(k=y/x=常数,即线性)

  • kCAAnimationDiscrete:



    到某个时间点,y值会跳跃式变化

  • kCAAnimationPaced:
    设为kCAAnimationPaced时,使让keyTimes与timeFunctions属性失效,整个动画都是匀速进行的。这里kCAAnimationLinear的曲线刚好和它一样,只是kCAAnimationLinear如果设置了keyTimes、timeFunctions的话,曲线会有所不同。

  • kCAAnimationCubic:



    和kCAAnimationLinear相比,它对y值变化的过渡更加圆滑,比如在y值在变化到第一个最高点v1时,切线的斜率有所减小,变化的速度减缓。

  • kCAAnimationCubicPaced:
    和kCAAnimationCubic相比,加了paced,两者之间关系类似kCAAnimationLinear与kCAAnimationPaced。

tensionValues、continuityValues、biasValues

这几个属性有兴趣的自己可以动手试验一下,观察曲线会有什么不同。

#6 - CATransition

继承自CAAnimation,转场动画。它提供的一系列预设的layer的动画。接着手动改变layer的属性,表现上就完成了过渡转场的实现。

属性 说明
type 转场类型,下面会列举
subtype 可选的子类型(使转场方向的实现不同)
startProgress 动画从某个进度处开始执行,取值范围为0到1,默认为0(跟使用播放器在某处起继续播放视频类似)
endProgress 取值同上,但要求大于startProgress,默认为1
filter 滤镜(**该属性iOS不支持)

转场动画中都包含了一个伪layer和最终呈现layer(手动改变layer属性将影响的layer),分别在下面称为faker和layer。

type

合法的转场动画类型有:
fade:默认。faker淡出,layer淡入
moveIn:layer移入覆盖faker
push:layer推入,faker推出
reveal:覆盖在layer上面的faker被移出

私有:(被苹果ban了,不建议直接使用)
cube:立方体旋转,layer将会在呈现的面,faker在不可见的面
suckEffect:覆盖在layer上面的faker被抽离
oglFlip:将背面的layer翻转到前面,faker翻转到背面、、
rippleEffect:伴随着水面波动动画,faker淡出,layer淡入
pageCurl:翻到下一页,faker被翻走,呈现layer
pageUnCurl:翻回上一页,layer被翻回并覆盖faker
cameraIrisHollowOpen:下面这两个是特殊的。镜头开,同时呈现部分为透明,而不是layer
cameraIrisHollowClose:类似上面,镜头关


subtype

4个子类型,表示上左下右4个转场动画方向:
fromTop
fromLeft
fromBottom
fromRight

代码例子:
// layer的初始状态,它将为faker的状态
layer.backgroundColor = [UIColor blueColor].CGColor;

CATransition *anim = [CATransition animation];
anim.type = @"fade";
anim.subtype = @"fromLeft";
anim.duration = 1.5f;
[layer addAnimation:anim forKey:@"anim"];

// addAnimation后,转场动画开始执行了,此时设置的状态为layer的状态
layer.backgroundColor = [UIColor orangeColor].CGColor;

#7 - CAAnimationGroup

动画组,一样继承自CAAnimation,只有自己的一个数组属性。当动画组开始执行,它的数组内引用的动画对象将会并发执行,实现多个动画同时渲染。

属性 说明
animations 数组类型(NSArray<CAAnimation *>),保存多个并发执行的动画对象

需要注意的是,数组内动画对象的delegate和removedOnCompletion将被忽略,因此设计时应该是设置Group的这两个属性。

// 动画1:3秒的大小缩放
CABasicAnimation *anim1 = [CABasicAnimation animation];
anim1.keyPath = @"transform.scale";
anim1.duration = 3.0;
anim1.fromValue = @[@1, @1, @1];
anim1.toValue = @[@0.8, @0.8, @1];// x,y,z轴上的缩放

// 在animationGroup里的动画,以下两个属性设置是不起作用的
anim1.removedOnCompletion = NO;
anim1.delegate = self;

// 动画2:5秒的逐渐透明
CABasicAnimation *anim2 = [CABasicAnimation animation];
anim2.keyPath = @"opacity";
anim2.duration = 5.0f;
anim2.toValue = @0;

// 动画组:10秒的动画组,虽然组内动画在3秒和5秒的时候已经结束了
// 但组是持续10秒的,10秒后才结束
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
animGroup.duration = 10.0f;
animGroup.animations = @[anim1, anim2];
[layer addAnimation:animGroup forKey:@"animGroup"];

如果想要layer的状态填充为动画结束时的状态,需要

anim1.fillMode = kCAFillModeForwards;
anim2.fillMode = kCAFillModeForwards;
animGroup.fillMode = kCAFillModeForwards;

但是Group结束时,Group内的动画仍然后被remove掉,让动画的fillMode效果消失,因此不能让Group被移除

animGroup.removedOnCompletion = NO;// Group内的动画设置此属性是无效的,只能通过Group来设置
#8 - CASpringAnimation

CASpringAnimation由iOS 9.0+ SDK开始支持,所以并不兼容9.0以下版本,要根据实际开发来选择使用。
继承自CABasicAnimation,弹簧动画。

CASpringAnimation类似于下面的场景:
一个被一端固定在墙上的弹簧钩住的物件,放在有摩擦力的地面某初始位置(fromValue)上,从弹簧压缩状态,手按住到释放物件,弹簧推动物件加速;
直到弹簧开始要被拉伸了,物件开始减速。减速到0时,弹簧收缩并拉回物件;
在拉回到弹簧原长的时候,由于这时物件有速度、动能,物件继续运动并因弹簧推(弹)力减速至0,弹簧再次被压缩;
如此反复左右振动,直至动能因摩擦力,能量完全损失至为0,物件停留在某个最终位置(toValue)。


换成动画来说,就是在动画时间内,layer某keyPath的fromValue在会在toValue附近振动并最终到达toValue。

属性 说明
mass 物件质量。增大mass,会使振动时偏离toValue的绝对值增大,<br />并使到达toValue的时间变长,即以下的settlingDuration值增加。<br />默认值为1
stiffness 弹性系数。增大stiffness,会使settlingDuration值减少。<br />默认值为100
damping 阻尼系数。增大damping,每次振动时偏离toValue的绝对值衰减更快,结果会使settlingDuration值减少。<br />默认值为10
initialVelocity 初速度增量。layer有个初始速度,当initialVelocity大于0,layer向toValue运动得更快(初始速度+initialVelocity);<br />当initialVelocity小于0,向toValue运动更缓慢。并在小于某个值时,会使layer开始动画时,初速度反向再逐渐向toValue运动
settlingDuration 由前面的物理性属性估算出的动画时长(到达toValue的所需时长),readonly只读。<br />有一点注意要的是,当设置了duration时,弹簧动画的实际有效时长为duration = duration>settlingDuration ? duration : settlingDuration<br />(duration与settlingDuration中值比较大的那个,它们很有可能不等)

QuartzCore补充

#9 -隐式动画

对于非view.layer的这种layer(称为非root layer),在代码中并没有编写动画的情况下,直接修改layer的属性,比如bounds,会有默认产生一个bounds缩放的过渡动画(称为隐式动画)。
至于修改非root layer的哪些属性会触发隐式动画,在上面CAPropertyAnimation章keyPath列举的属性都会触发。
隐式动画类型为CABasicAnimation,默认duration = 0.25秒。

#10 - CATransaction

动画事务类。它是对于如何更新渲染layer树的一套机制。渲染树详请可以看CALayer篇,可以认为当修改layer的属性就会触发CATransaction机制。
动画事务可以指定怎样去更新layer,比如开启关闭默认的隐式过渡动画(上面所说的),动画的默认有效时长、时间函数等,而且还可以指定事务完成后进行回调。

先来看下类的方法,再介绍动画事务的使用。

方法 说明
+ (void)begin 开启一个动画事务
+ (void)commit 提交一个动画事务
+ (void)flush 立即执行当前事务(立即渲染layer、动画)
+ (void)lock 加锁,事务内对layer属性的读写是原子性的
+ (void)unlock 释放锁
+ (CFTimeInterval)animationDuration 返回事务内隐式动画的有效时长
+ (void)setAnimationDuration:(CFTimeInterval)dur 设置事务内隐式动画的有效时长
+ (nullable CAMediaTimingFunction *)animationTimingFunction 返回事务内隐式动画的时间函数
+ (void)setAnimationTimingFunction:(nullable CAMediaTimingFunction *)function 设置事务内隐式动画的时间函数
+ (BOOL)disableActions 是否启用默认行为(执行隐式动画)
+ (void)setDisableActions:(BOOL)flag 开启或关闭默认行为
+ (nullable void (^)(void))completionBlock 返回事务内所有动画都结束时回调的block
+ (void)setCompletionBlock:(nullable void (^)(void))block 设置事务内所有动画都结束时回调block
+ (nullable id)valueForKey:(NSString *)key 返回当前事务属性key对应的值value,和上面的getter方法起相同的作用,key有:<br />kCATransactionAnimationDuration、<br />kCATransactionDisableActions、<br />kCATransactionAnimationTimingFunction、<br />kCATransactionCompletionBlock
+ (void)setValue:(nullable id)anObject forKey:(NSString *)key 类似的,设置当前事务属性对应的值

举一些常用的动画事务使用例子:

  • 修改隐式动画的有效时长:
CALayer *layer = [CALayer layer];
layer.bounds = (CGRect){0, 0, 50, 50};
// ..
[CATransaction begin];
layer.bounds = (CGRect){0, 0, 100, 100};// 上面说了,将会默认执行0.25秒的隐式动画
[CATransaction setAnimationDuration:2.0f];// 手动指定隐式动画的有效时长,改为2秒
[CATransaction commit];
  • 或者说,想要改变layer的bounds而不想要它默认的过渡动画效果的话:
[CATransaction begin];
layer.bounds = (CGRect){0, 0, 100, 100};
[CATransaction setDisableActions:NO];// 关闭默认行为(隐式动画)
[CATransaction commit];
  • 再来个例子,修改UIScrollView - setContentOffset:animated:的滑动动画时间,并且滑动动画结束后,进行打印:
[CATransaction begin];
[CATransaction setAnimationDuration:3.0f];// 手动指定3.0秒
[CATransaction setCompletionBlock:^{NSLog(@"scroll anim done!");}];// 设置回调
[scrollview setContentOffset:(CGPoint){0,150} animated:YES];
[CATransaction commit];
  • 最后这个是比较旁门小技巧,子线程立即更新UI。wow,amazing!但是并不推荐使用。不出现多线程问题的情况下,在子线程对UI的渲染是有延迟的,但是可以通过事务的flush解决延迟:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    CALayer *layer = [CALayer layer];
    layer.bounds = CGRectMake(0, 0, 200, 200);
    layer.backgroundColor = [UIColor redColor].CGColor;
    // ..
    [self.view.layer addSublayer:layer];

    [CATransaction flush];// 立即渲染
});
关于事务

事务的种类:
1、显式事务:手动调用CATransaction begin/commit创建的事务
2、隐式事务:当layer层级关系或layer的属性发生变更,系统会在当前线程自动创建一个事务,并在进行下一轮runloop时自动commit(不了解runloop的可以百度一下)

事务的执行:
直到调用flush,事务才会被执行,此时渲染layer或动画。虽然前几个例子都没有手动flush,但是到了runloop一轮的末尾,所有被commit了的事务的flush都会被调用。
(苹果出于对性能的考虑,建议不要手动调用flush,应该统一由runloop过程调用)

看回最后一个例子,结合上面所讲的,flush是flush当前的隐式事务。而且
非主线程的runloop默认不开启,所以必须手动调用flush才能获得我们想要的结果。

参考

官方文档1:Core Animation Programming Guide
官方文档2:Animation Types and Timing Programming Guide

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

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,465评论 6 30
  • 前言 本文只要描述了iOS中的Core Animation(核心动画:隐式动画、显示动画)、贝塞尔曲线、UIVie...
    GitHubPorter阅读 3,600评论 7 11
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,094评论 5 13
  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 3,076评论 1 23
  • 书写的很好,翻译的也棒!感谢译者,感谢感谢! iOS-Core-Animation-Advanced-Techni...
    钱嘘嘘阅读 2,289评论 0 6