看到工作很多年的同事,对于app发生的一些异常现象,不够重视,也没有很准确的分析到原因,为什么自己写的动画在某种场景下停下来了,而且是属于非常偶现的, 有些类的delloc没有调用,特此再过一遍基础,学而则精.
CAAnimation 动画抽象超类
@interface CAAnimation : NSObject
<NSSecureCoding, NSCopying, CAMediaTiming, CAAction>
从上面看到 CAAnimations是一个抽象超类,遵循了CAMediaTiming和CAAction协议,可以设置动画的开始时间、持续时间、速度、时间偏移、重复次数等

CAAnimation的一些派生类:
- CAPropertyAnimation: 支持动画地显示图层的keyPath,一般不直接使用。
- CATransition: 提供渐变效果:(推拉push效果,消退fade效果,揭开reveal效果)
- CAAnimationGroup: 允许多个动画同时播放
- CABasicAnimation: 提供了对单一动画的实现
- CAKeyframeAnimation: 关键桢动画,可以定义行动路线
- CASpringAnimation: 实现弹簧效果的动画
比较常用的是CABasicAnimation、CAKeyframeAnimation、CAAnimationGroup和CASpringAnimation;
协议——CAMediaTiming
-
beginTime: 动画的开始时, 默认为0
-
duration: 动画持续时间,默认为0
-
speed: 动画执行速度
-
timeOffset: 时间偏移量,默认为0
-
repeatCount: 重复次数,默认为0
-
repeatDuration: 重复间隔,默认为0
-
autoreverses: 动画自动逆向执行,默认为No
-
fillMode: 决定当前对象在应用程序非active时间段的行为。fillMode取值fillMode属性值
kCAFillModeRemoved 这个是默认值,也就是说当动画开始前和动画结束后, 动画对layer都没有影响,动画结束后,layer会恢复到之前的状态 kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态 kCAFillModeBackwards 在动画开始前,只需要将动画加入了一个layer, layer便立即进入动画的初始状态并等待动画开始。 kCAFillModeBoth 这个其实就是上面两个的合成.动画加入后开始之前, layer便处于动画初始状态,动画结束后layer保持动画最后的状态
协议-CAAction
- (void)runActionForKey:(NSString *)event object:(id)anObject
arguments:(nullable NSDictionary *)dict;
协议-NSCopying
协议-NSSecureCoding 数据归档编码解码
timeFunction 速度控制函数,控制动画运行的节奏
kCAMediaTimingFunctionLinear:线性动画
kCAMediaTimingFunctionEaseIn:动画缓慢开始,然后逐渐加速
kCAMediaTimingFunctionEaseOut:动画迅速开始,在结束时减速
kCAMediaTimingFunctionEaseInEaseOut:动画慢慢开始,然后加速,在结束之前减速
kCAMediaTimingFunctionDefault :系统默认动画效果
delegate 动画代理
animationDidStart: 动画开始时调用
animationDidStop:finished: 动画结束后调用
removedOnCompletion
/* When true, the animation is removed from the render tree once its **active** duration has passed. Defaults to YES. */
如果是true,一旦离开了活跃的动画时间,动画将从渲染树中移除。
delegate和removedOnCompletion
CATransition 转场动画
-
type: 动画类型,有两种表达方式,字符串或系统宏,如:@"push"/kCATransitionPush
kCATransitionFade kCATransitionMoveIn kCATransitionPush kCATransitionReveal fade:交叉淡化过渡 push:新视图把旧视图推出去 moveIn:新视图移到旧视图上面 reveal:将旧视图移开,显示下面的新视图 oglFlip:上下左右翻转效果 cube:立方体翻滚效果 suckEffect:收缩效果 rippleEffect:水滴效果 pageCurl:向上翻页效果 pageUnCurl:向下翻页效果 cameraIrisHollowOpen:相机镜头打开效果 cameraIrisHollowClose:相机镜头关闭效果
-
subtype: 指定动画的方向
kCATransitionFromRight kCATransitionFromLeft kCATransitionFromTop kCATransitionFromBottom
-
startProgress: 定义动画的开始点 范围0-1
-
endProgress:定义动画的结束点 值必须大于结束点 范围0-1

动画实现:
CATransition *anim = [CATransition animation];
// 设置转场类型
anim.type = @"cube";
// 设置动画的方向
anim.subtype = kCATransitionFromLeft;
anim.startProgress = 0.1;
anim.endProgress = 0.5;
anim.removedOnCompletion = NO;
anim.fillMode = kCAFillModeBoth;
anim.duration = 0.3;
[self.tranImageView.layer addAnimation:anim forKey:@"transition"];
CAPropertyAnimation
CAPropertyAnimation也是一个抽象类,不能使用该类直接来完成动画!
keyPath 指定接收层动画的关键路径 如:transform.rotation.x(绕着x轴旋转)
cumulative 下一次动画执行是否接着刚才的动画,默认为false
additive 如何处理多个动画在同一时间段执行的结果,若为true,同一时间段的动画合成为一个动画,默认为false。(使用 CAKeyframeAnimation 时必须将该属性指定为 true ,否则不会出现期待的结果)
keyPath : Animatable
CABasicAnimation 基础动画
- fromValue:keyPath相应属性的初始值
- byValue:keyPaht相应属性的相对插值
- toValue:keyPath相应属性的结束值
圆圈进度动画 如实现下载进度实现

动画实现:
- (void)drawCircleAn

](https://img.haomeiwen.com/i2906485/dec574af6248c58d.gif?imageMogr2/auto-orient/strip)
](https://img.haomeiwen.com/i2906485/9c211991cb638631.gif?imageMogr2/auto-orient/strip)
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.fromValue = @0;
animation.toValue = @1;
animation.duration = 3;
CAShapeLayer *shapeLayer = [self drawCircle];
[shapeLayer addAnimation:animation forKey:@"circleAnimation"];
// 将CAShaperLayer放到View上显示
[self.view.layer addSublayer:shapeLayer];
}
- (CAShapeLayer *)drawCircle {
CAShapeLayer *circleLayer = [CAShapeLayer layer];
// 指定frame,只是为了设置宽度和高度
circleLayer.frame = CGRectMake(0, 0, 100, 100);
// 设置居中显示
circleLayer.position = self.view.center;
// 设置填充颜色
circleLayer.fillColor = [UIColor clearColor].CGColor;
// 设置线宽
circleLayer.lineWidth = 4.0;
// 设置线的颜色
circleLayer.strokeColor = [UIColor purpleColor].CGColor;
circleLayer.fillColor = [UIColor whiteColor].CGColor;
// 使用UIBezierPath创建路径
UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50, 0) radius:50 startAngle:-M_PI_2 endAngle:1.5*M_PI clockwise:YES];;
// 设置CAShapeLayer与UIBezierPath关联
circleLayer.path = circlePath.CGPath;
return circleLayer;
}
CAKeyframeAnimation 关键帧动画
- values:关键帧动画值的数组,当path为nil时设置有效,否则优先选择属性path做动画
- path:动画执行的点路径(通过Core Graphics提供的API来绘制路径),设置了path,values将被忽略
- keyTimes:关键帧动画每帧动画开始执行时间点的数组,取值范围为0~1,数组中相邻两个值必须遵循后一个值大于或等于前一个值,并且最后的值不能为大于1。设置的时候与calculationMode有关. (n-1)个CAMediaTimingFunction.
- timingFunctions:动画执行效果数组
kCAMediaTimingFunctionLinear(线性):匀速,给你一个相对静态的感觉
kCAMediaTimingFunctionEaseIn(渐进):动画缓慢进入,然后加速离开
kCAMediaTimingFunctionEaseOut(渐出):动画全速进入,然后减速的到达目的地
kCAMediaTimingFunctionEaseInEaseOut(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。
kCAMediaTimingFunctionDefault(默认时间函数)
- calculationMode:关键帧时间计算方法,每帧动画之间如何过渡,类似与UIView的keyframeAnimation。
kCAAnimationLinear 默认值,表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算,该模式提供了最大化控制动画的时间
kCAAnimationDiscrete 离散的,不进行插值计算,所有关键帧直接逐个进行显示,该模式使用keyTimess属性,但是忽略timingFunctions属性。
kCAAnimationPaced 使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions无效
kCAAnimationCubic 对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,这里的主要目的是使得运行的轨迹变得圆滑
kCAAnimationCubicPaced 看这个名字就知道和kCAAnimationCubic有一定联系,其实就是在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的
- tensionValues: 控制着曲线的紧密度(正值将越紧,负值将越宽松)
- continuityValues: 控制片段之间的链接(正值将有锋利的圆角,负值将是倒立的圆角)
- biasValues:定义了曲线发生的地点(正值将在在控制点前移动曲线,负值将在控制点后移动)
- rotationMode:设置路径旋转,当设置path有不同角度时,会自动旋转layer角度与path相切

动画代码实现:
- (void)pointMoveAn
{
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = [self starPath].CGPath;
layer.strokeColor = [UIColor blueColor].CGColor;
layer.fillColor = [UIColor lightGrayColor].CGColor;
[self.view.layer addSublayer:layer];
[self.view addSubview:self.pointView];
UIBezierPath *path = [self starPath];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.path = path.CGPath;
animation.duration = 4;
animation.repeatCount = MAXFLOAT;
animation.fillMode = kCAFillModeForwards;
[path moveToPoint:CGPointMake(30, 300)];
[path moveToPoint:CGPointMake(30, 300)];
[self.pointView.layer addAnimation:animation forKey:@"starAnimation"];
}
- (UIBezierPath *)starPath
{
UIBezierPath *path = [UIBezierPath new];
[path moveToPoint:CGPointMake(47, 338)];
[path addLineToPoint:CGPointMake(105, 330)];
[path addLineToPoint:CGPointMake(131, 278)];
[path addLineToPoint:CGPointMake(157, 330)];
[path addLineToPoint:CGPointMake(216, 338)];
[path addLineToPoint:CGPointMake(174, 378)];
[path addLineToPoint:CGPointMake(183, 436)];
[path addLineToPoint:CGPointMake(131, 409)];
[path addLineToPoint:CGPointMake(79, 436)];
[path addLineToPoint:CGPointMake(89, 378)];
[path closePath];
path.lineWidth = 1.0f;
return path;
}
CAAnimationGroup 动画组
- animations: CAAnimation集合
使得多个动画同一时间段内并发执行,执行一些单一动画不易实现的效果.
如下面一段动画,既要改变路径也要改变图形的圆角大小.

动画代码实现:
CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.values = @[[NSValue valueWithCGPoint:CGPointMake(0, 0)],[NSValue valueWithCGPoint:CGPointMake(100, 100)],[NSValue valueWithCGPoint:CGPointMake(0, 200)]];
animation.duration = 2;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.keyTimes = @[@0,@0.25,@0.5,@0.75,@1];
animation.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 200, 100, 100)].CGPath;
CABasicAnimation * animation2 = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
animation2.toValue = @50;
animation2.duration = 2;
animation2.fillMode = kCAFillModeForwards;
animation2.removedOnCompletion = NO;
CAAnimationGroup * group = [CAAnimationGroup animation];
group.duration = 2;
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
group.animations = @[animation,animation2];
[self.testView.layer addAnimation:group forKey:@"group"];
CASpringAnimation是iOS9之后开放出来的API
- mass: 质量,影响图层运动时的弹簧惯性,质量越大,弹簧拉伸和压缩的幅度越大.默认值:1;
- stiffness: 刚度系数(劲度系数/弹性系数),刚度系数越大,形变产生的力就越大,运动越快.值必须大于0. 默认值:100;
- damping:阻尼系数,阻止弹簧伸缩的系数,阻尼系数越大,停止越快.默认值:10;
- initialVelocity:速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反.默认值:0;
- settlingDuration:估算时间 返回弹簧动画到停止时的估算时间,根据当前的动画参数估算

动画实现
- (void)springAnimationTextAction:(CGPoint)point {
if (@available(iOS 9.0, *)) {
CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:@"bounds"];
springAnimation.fromValue = [NSValue valueWithCGRect:CGRectMake(point.x, point.y, 60, 60)];
springAnimation.toValue = [NSValue valueWithCGRect:self.customView.frame];
springAnimation.mass = 5;
springAnimation.stiffness = 100;
springAnimation.damping = 10;
//速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反
springAnimation.initialVelocity = 10;
//估算时间 返回弹簧动画到停止时的估算时间,根据当前的动画参数估算
NSLog(@"====%f",springAnimation.settlingDuration);
springAnimation.duration = springAnimation.settlingDuration;
springAnimation.removedOnCompletion = NO;
[self.customView.layer addAnimation:springAnimation forKey:@"springAnimation"];
} else {
// Fallback on earlier versions
}
}
CA动画的基础知识点告一段落了,现在来说一下我们现实开发中可能遇到的一些易错或难点:
fillMode:首先是fillMode,不知道你有没有注意到上面的非active时间段解释,该处于的阶段表示“动画开始之前和动画结束之后”, 动画还有可能被一些突发事件终止,如页面跳转,前后台切换等都会是原先的动画离开active状态.接着看下一个属性
removedOnCompletion: 它同样也是有 active duration相关解释,表示如果为true,动画一旦处于非active段,将会从渲染树移除,如果为false,它会一直存在于内存中,直到图层被销毁。
so,为了防止一些动画被打断,如下拉刷新的自定义loading菊花,需要加上这个属性,否则在页面处于加载的状态时,push下个页面,再pop回来,如果实际还处于loading状态,你就会发现loading动画失效了,就出现UI展示Bug了!
delegate: 有没有注意的它的属性是<strong>, 这样的话我们的容器类会强持有这个animation. 当delegate和removedOnCompletion一同出现的时候,需要手动移除这个animation,否则类不会被释放!
keyPath: keyPath有哪些值,你了解不?为什么那些值可以作为动画的keyPath? 我们知道动画操作在Layer上, CALayer.h 对属性有声明,每一个可以作为keyPath的属性都会有声明Animatable,可动画性!
总结: CAAnimation是一个抽象超类,OC语言也是有抽象类的!
CAAnimation是一个很好的API接口示范例子,继承能实现很多功能,协议可以有不一样的使用方法.
CAAnimation 和CALayer互不相离, CAAnimation与UIView的animation又有怎么样的关系? 下回分析.
网友评论