文章结构
- 动画作用域
- 常用动画属性
- 动画类结构
- 动画类的讲解
本文主要讲解动画基本知识,只要搞懂了基础,看似炫酷的复杂动画其实就不难,复杂的动画都是由一个个简单的动画拼接而成的,其实任何事物也是如此。
动画作用域
所有的动画都是作用于CALayer这个类或者子类,视图都自带一个默认的CALayer类型的图层,我们要做的动画就是改变这个图层上的一些属性来达到目的,哪些属性可以动画,开发文档里都有说明,而且属性值的类型需要转换成特定的数值类型才可以,我做了一个表格汇总如下:
比如我们要在x轴上平移,则需要修改position属性的x坐标,坐标是CGFloat类型的数据,我们需要把它转成NSNumber类型,然后赋值给代表属性变化的变量,如CABasicAnimation和fromeValue、toValue、byValue等。
常用动画属性
先来看一个代码
CABasicAnimation *animation_1 = [CABasicAnimation animationWithKeyPath: @"position.y"];
这是一个设置在y轴上平移的代码,最后一个参数是不是感觉好无语,我要平移或者旋转该如何设置呢?其实用得最多的莫过于平移和旋转,可以在x轴、y轴或者同时在两个轴上移动,都是通过设置position这个属性来设置的。旋转最常用的就是transform.rotation.z沿z轴旋转,transform的其他设置还有平移transform.translation、缩放transform.scale,分别也有相对应的x、y、z方向,可以自己玩玩查看相对应效果。
其实,还有好多动画的属性比如颜色,透明度等,多得跟米一样。一般情况我们都是通过点击某个控件后,让其实现一些炫酷的效果,这些效果用得最多的就是平移和缩放、旋转
等的结合。
动画类结构
先来看一张图,如下:
所有的动画都是继承CAAnimation,此为所有动画的父类,而且遵循CAMediaTiming协议,箭头代表继承关系。其中,CAPropertyAnimation是一个抽象类,不能直接使用,一般都是使用它的子类应用到layer中。
动画类的讲解
主要讲解常用的一些属性
CABasicAnimation
- fromValue 属性的开始值
- toValue 属性的结束值
- byValue 属性变化的相对值,可以理解为toValue减去fromValue,当不需要明确开始和结束的具体值时,可以只设置该属性
该类是最基础的动画类,只包含两点间的动画,使用方法一:设置layer某个属性的开始值fromValue,结束值toValue;方法二:只设置byValue;方法三:设置fromValue和byValue,相当于toValue等于fromValue加上byValue;方法四:设置toValue和byValue,相当于fromValue等于toValue减去byValue。常用的搭配使用就这四种了,也是比较容易理解的。
使用示例:实现在y轴上的动画
- (CABasicAnimation *)configureBasicTranslationAnimationFromValue:(id)fromeValue toValue:(id)toValue byValue:(id)byValue{
//平移动画
CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath: @"position.y"];
//这个if clause只是为了演示方便,项目在中请根据实际情况处理相关逻辑
if (byValue) {
//该属性表示相对值,如果不需要明确开始和结束的值,可以使用该属性代替fromValue和toValue
basicAnimation.byValue = byValue;
}else{
basicAnimation.fromValue = fromeValue;
basicAnimation.toValue = toValue;
}
//fillMode和removedOnCompletion搭配,最常用的就是保持动画结束时候的状态,除非有必要,否则不要这样设置,会有离屏渲染,增加性能损耗
// basicAnimation.fillMode = kCAFillModeForwards;
// basicAnimation.removedOnCompletion = NO;
if (_animationType != AnimationTypeGroup) {
basicAnimation.duration = 1.0;//动画时间
basicAnimation.repeatCount = 1;
[_animationView.layer addAnimation:basicAnimation forKey:nil];
}
return basicAnimation;
}
示例代码:自转
- (CABasicAnimation *)configureCenterRotationAnimation{
//旋转动画:最常用的就是围绕z轴旋转transform.rotation.z,transform的其他设置还有平移transform.translation、缩放transform.rotation,分别也有相对应的x、y、z方向,可以自己玩玩查看相对应效果
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotateAnimation.fromValue = @(0);
rotateAnimation.toValue = @(M_PI * 2);
if (_animationType != AnimationTypeGroup) {
rotateAnimation.duration = 1.0;
rotateAnimation.repeatCount = MAXFLOAT;//MAXFLOAT无限循环
[_animationView.layer addAnimation:rotateAnimation forKey:nil];
}
return rotateAnimation;
}
CAKeyframeAnimation
帧动画,可以包含多个点,或者指定动画的路径。
注意:如果你的试图动画是由多个点组成的建议使用路径来代替,方便简单而且不容易出错。
- values 是一个数组包含动画轨迹的各个值。
- path 动画的路径,一般设置了values就不要设置path。
- keyTimes 一个数组,代表各个动画节点的时间点,而且是一个递增的NSNumber类型数据,而且元素个数必须和动画节点数一致,否则可能达不到你要的动画效果。如果calculationMode = kCAAnimationLinear or kCAAnimationCubic,keytimes属性中的第一个数必须是0,最后一个必须是1;如果是kCAAnimationDiscrete,keytimes属性中的第一个数必须是0,最后一个数必须是1,而且keytimes属性的数组元素个数比values中的多一个;如果是kCAAnimationPaced or kCAAnimationCubicPaced,则该属性不起作用。
- timingFunctions 这个应该不陌生了,比如你的动画由A->B->C->D,则可以定义A->B,B->C,C->D之间的动画效果,先快后慢还是先慢后快等效果。如果你的动画节点即values属性或者path路径的节点数为n,则该属性个数应该为n-1个。
- calculationMode 据我的理解,该属性表示帧与帧之间的转换模式,比如设置成kCAAnimationCubic表示从AB段转换到BC段时,有一个弧度,而不是一个直角似的生硬,前提是这连个线程不在同一条直线上,要不然看不出效果。
- rotationMode 旋转模式,是否打开自旋转,可以这样比喻地球围绕太阳公转,地球也有自转。若不需要自转则不用设置该属性
示例代码:斜射火箭
- (void)configureKeyframesAnimation{
_animationView.transform =CGAffineTransformMakeRotation(M_PI_4);
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
CGPoint point_1 = CGPointMake(_animationView.center.x, _animationView.center.y);
CGPoint point_2 = CGPointMake(point_1.x + 20, point_1.y - 20);
CGPoint point_3 = CGPointMake(point_2.x + 130, point_2.y - 130);
CGPoint point_4 = CGPointMake(point_3.x, point_1.y);
NSValue *value_1 = [NSValue valueWithCGPoint:point_1];
NSValue *value_2 = [NSValue valueWithCGPoint:point_2];
NSValue *value_3 = [NSValue valueWithCGPoint:point_3];
NSValue *value_4 = [NSValue valueWithCGPoint:point_4];
animation.values = @[value_1, value_2,value_3,value_4];
animation.repeatCount = 1;
animation.duration = 3.0f;
//一般来讲该属性的count必须和values属性的count一致,且后一个数必须大于或者等于后一个数
//如果calculationMode = kCAAnimationLinear or kCAAnimationCubic,keytimes属性中的第一个数必须是0,最后一个必须是1;如果是kCAAnimationDiscrete,keytimes属性中的第一个数必须是0,最后一个数必须是1,而且keytimes属性的数组元素个数比values中的多一个;如果是kCAAnimationPaced or kCAAnimationCubicPaced,则该属性不起作用。
animation.keyTimes = @[@(0.0), @(0.2), @(0.6), @(1.0)];
animation.calculationMode = kCAAnimationCubic;
animation.delegate = self;
[_animationView.layer addAnimation:animation forKey:nil];
}
示例代码:路径动画
- (CAKeyframeAnimation *)configurePathRotationAnimation{
//帧动画
//按照路径进行动画,该例子中是围绕一个圆进行动画
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path, NULL, _animationView.center.x, _animationView.center.y, 80, M_PI * 2, 0, true);
CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
keyAnimation.path = path;
CGPathRelease(path);
//该属性表示动画结束后会逆向原路返回远点
// keyAnimation.autoreverses = YES;
//旋转模式,是否打开自旋转,可以这样比喻地球围绕太阳公转,地球也有自转。若不需要自转则不用设置该属性
keyAnimation.rotationMode = kCAAnimationRotateAuto;
//匀速,其他效果自己玩
keyAnimation.calculationMode = kCAAnimationPaced;
if (_animationType != AnimationTypeGroup) {
keyAnimation.duration = 5.0;
keyAnimation.repeatCount = 1;
[_animationView.layer addAnimation:keyAnimation forKey:nil];
}
return keyAnimation;
}
CASpringAnimation
弹簧动画,这是ios9添加的动画。应该都学过胡克定律,不记得的可以百度下。为了更好的理解,最好先去了解下。
- mass 质量:值越大,弹性效果越强,振动次数越多,settlingDuration越大。
- stiffness 弹簧的劲度(刚度):值越大,振动次数越少,settlingDuration越小。
- damping 阻尼:值越大,settlingDuration 越小。
- initialVelocity 初速度:有初速度的话,settling time肯定越大, 正负值只是代表方向。
- settlingDuration 据我的理解,这是根据你设定的以上四个参数,计算出来的弹簧从开始运动到静止所需要的时间,该值只读的,这个不一定和你设定的动画时间一样,比如需要10秒弹簧才停止振动,但是你给的动画时间就3秒,之后就停止,这也是可以的。
mass、stiffness、damping、initialVelocity这几个参数都会影响到settlingDuration的值。
示例代码:
- (void)configureSpringAnimation{
//弹簧动画
CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:@"position.x"];
springAnimation.duration = 3.0;
springAnimation.repeatCount = 1.0;
springAnimation.fromValue = @(50);
springAnimation.toValue = @([UIScreen mainScreen].bounds.size.width - 50);
springAnimation.delegate = self;
//阻尼:值越大 settling time 越小
springAnimation.damping = 1;
//质量:值越大 弹性效果越强 振动次数越多 settling time越大
// springAnimation.mass = 0.5;
//弹簧的劲度(刚度):值越大 振动次数越少 settling time越小
// springAnimation.stiffness = 10;
//初速度 有初速度的话 settling time肯定越大, 正负值只是代表方向
springAnimation.initialVelocity = -5;
[_animationView.layer addAnimation:springAnimation forKey:nil];
NSLog(@"settling time is %f", springAnimation.settlingDuration);
}
CATransition
转场动画,可以应用在两个控制器之间的转换,或者两个试图之间的转换。
- type 转场的类型,具体效果可以运行代码试试,最直观
- subtype 定义转场如何地进行
示例代码:两个控制器之间的转换,我在Demo里已经注释掉了,如果想看效果,首先要把storybaord上的从tableviewcell到AnimationViewController 的seague去掉。
AnimationViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:@"AnimationViewController"];
//转场动画
CATransition *transition = [CATransition animation];
//切换其他值试下效果
transition.type = kCATransitionReveal;
//打开这个玩玩
// transition.subtype = kCATransitionFromTop;
transition.duration = 0.25;
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[self.navigationController pushViewController:vc animated:NO];
CAAnimationGroup
组动画,就是一些动画的集合,那些复杂的动画就是通过组动画来实现的。
- animations 数组:各个独立的动画。注意各个独立的动画之间不要有冲突。
示例代码:
- (void)configureGroupAnimation{
//组合动画:动画间不能有冲突,又要按圆路径动画,又要沿y轴移动,这很明显不行的,可以打开数组的那个注释试下效果.
//如果还 需要其他 动画,可以继续添加.
CABasicAnimation *basicAnimation = [self configureBasicTranslationAnimationFromValue:nil toValue:nil byValue:@(-200)];
CABasicAnimation *centerRotateAnimation = [self configureCenterRotationAnimation];
//CAKeyframeAnimation *pathAnimation = [self configurePathRotationAnimation];
CAAnimationGroup *groupAnimations = [CAAnimationGroup animation];
groupAnimations.animations = @[basicAnimation, centerRotateAnimation/*, pathAnimation*/];
groupAnimations.duration = 2.0;
groupAnimations.repeatCount = 1;
[_animationView.layer addAnimation:groupAnimations forKey:nil];
}
CAAnimationDelegate
动画代理,如果你想监测动画什么时候开始和结束可以设置代理。不要说不会设置代理哦-_-
CAMediaTiming
所有动画类都遵从的协议,通过设置这里的属性你可以开始或者暂停动画、设置动画时间、设置动画的开始时间、动画的重复次数等,下面一一讲解下常用属性怎么用。
- beginTime 动画开始时间。如果你的视图有多个动画,则可以通过设置该属性让一些动画先开始,再继续其他动画。
- duration 整个动画的时长
- speed 动画的速度,一般该属性和timeOffset、beginTime这两个属性来实现继续或者暂停动画。很少有暂停动画这种实现的,项目里没用过。
- repeatCount 动画重复次数
- autoreverses 动画结束后是否原路返回
- fillMode 该属性最常用的就是保持动画结束时的状态,需要和CANimaiton类的removedOnCompletion属性配合使用。除非有必要,否则最好不要这么做,会导致离屏渲染,性能损耗。
示例代码:暂停动画
- (void)stopAnimation:(CALayer *)layer{
CFTimeInterval pauseTime = [layer convertTime:CACurrentMediaTime() toLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pauseTime;
NSLog(@"begin time: %f, pause time: %f", layer.beginTime, pauseTime);
}
示例代码:继续动画
- (void)resumeAnimation:(CALayer *)layer{
CFTimeInterval pauseTime = layer.timeOffset;
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval currentTime = [layer convertTime:CACurrentMediaTime() toLayer:nil];
CFTimeInterval timeSincePause = currentTime - pauseTime;
NSLog(@"pause time: %f, current time: %f, timeoffset : %f", pauseTime, currentTime, timeSincePause);
layer.beginTime = timeSincePause;
}
示例代码:beginTime使用,一个动画结束再开始另外一个动画
- (void)configureOnebyoneAnimation{
//上升
CABasicAnimation *animation_1 = [CABasicAnimation animationWithKeyPath: @"position.y"];
animation_1.byValue = @(-200);
animation_1.duration = 1.0;//动画时间
//下降
CABasicAnimation *animation_2 = [CABasicAnimation animationWithKeyPath:@"position.y"];
animation_2.fromValue = @(_animationView.frame.origin.y - 200);
animation_2.toValue = @(_animationView.frame.origin.y);
animation_2.duration = 1.0;
animation_2.beginTime = CACurrentMediaTime() + 1;
//无限自转
CABasicAnimation *animation_3 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation_3.fromValue = @(0);
animation_3.toValue = @(M_PI * 2);
animation_3.duration = 0.2;
animation_3.repeatCount = MAXFLOAT;
animation_3.beginTime = CACurrentMediaTime() + 1;
//回到原点去除动画
[NSTimer scheduledTimerWithTimeInterval:2 repeats:NO block:^(NSTimer * _Nonnull timer) {
[_animationView.layer removeAllAnimations];
}];
[_animationView.layer addAnimation:animation_1 forKey:nil];
[_animationView.layer addAnimation:animation_2 forKey:nil];
[_animationView.layer addAnimation:animation_3 forKey:nil];
}
最后Demo在此,欢迎下载交流,觉得有用的话,别忘记start哦😄