这篇文章主要是参考了官方文档 Core Animation Programming Guide
,加上自己个人的demo整理出来自用的,也贴出来供大家一起参考。
概览
Core Animation属于QuartzCore框架中的一部分,( = Core Animation + CALayer类及其派生类 + 少部分其它类与协议),其中:
CALayer = 渲染模型,持有渲染的几何图层的原始数据。
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)];
都是一样效果:
此外,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