上一篇iOS核心动画详解1说的很清楚了,那么下面详细介绍。
那么这里,将介绍上面所提到的7个类
和1个协议
具体的细节。
1:CAMediaTiming
协议
CAMediaTiming
协议中定义了时间,速度,重复次数等。属性定义如下:
beginTime
-> 用来设置动画延时,若想延迟1秒,就设置为CACurrentMediaTime()+1,其中CACurrentMediaTime()为图层当前时间。duration
-> 动画的持续时间。speed
-> 动画速率,决定动画时间的倍率。当speed为2时,动画时间为设置的duration的1/2。timeOffset
-> 动画时间偏移量。比如设置动画时长为3秒,当设置timeOffset为1.5时,当前动画会从中间位置开始,并在到达指定位置时,走完之前跳过的前半段动画。repeatCount
-> 动画的重复次数。repeatDuration
-> 动画的重复时间。autoreverses
-> 动画由初始值到最终值后,是否反过来回到初始值的动画。如果设置为YES,就意味着动画完成后会以动画的形式回到初始值。fillMode
-> 决定当前对象在非动画时间段的行为.比如动画开始之前,动画结束之后。
fillMode详细说明:试想这样一个问题:在beginTime非0(即动画未真正执行之前),以及removeOnCompletion被设置为NO的动画结束时,我们会遇到这样一个问题:被设置动画的属性应该是什么值?
一种可能是属性与动画没被添加之前保持一致,还有一种可能是保持动画开始之前那一帧或者动画结束那一帧,这就是所谓的填充。
CAMediaTiming的fillMode用来控制填充效果,它是一个NSString类型,有四种常量可供使用:
kCAFillModeRemoved (default) NSString 默认值,动画开始前和结束后,动画对图层都没有影响,图层依然保持初始值
kCAFillModeForwards NSString 动画结束后,图层一直保持动画后的最终状态
kCAFillModeBackwards NSString 动画开始前,只要加入动画就会处于动画的初始状态
kCAFillModeBoth NSString 综合了kCAFillModeForwards与kCAFillModeBackwards特性;
(动画加入图层到真正执行动画的时间段里,图层保持动画初始状态;动画结束之后保持动画最终状态)
其实不只是CAAnimation
遵循CAMediaTiming
协议,熟悉底层结构的小伙伴们应该知道CALayer
也遵循这个协议,所有在一定程度上我们可以通过控制layer本身
的协议属性来控制动画节奏。
2:CAAnimation
核心动画基础类
CAAnimation
核心动画基础类,不能直接使用。除了CAMediaTiming
协议中的方法,增加了CAAnimationDelegate
的代理属性等。关于它的定义如下:
@interface CAAnimation : NSObject<NSSecureCoding, NSCopying, CAMediaTiming, CAAction>
@property(nullable, strong) CAMediaTimingFunction *timingFunction;
@property(nullable, strong) id <CAAnimationDelegate> delegate;
@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;
@end
属性如下:
timingFunction
-> 动画缓冲属性。动画实际上就是在一段时间内随着某个特定速率执行变化的过程,现实中的任何物体都会在运动中经历加速或者减速的过程,而不是速度骤变;因此,CoreAnimation也内嵌了一系列标准的缓冲函数来使动画看起来更平滑自然,这就是我们要说到的动画缓冲。控制动画的节奏。系统提供的包括:kCAMediaTimingFunctionLinear
(默认,匀速执行动)kCAMediaTimingFunctionEaseIn
(慢进快出)kCAMediaTimingFunctionEaseOut
(快进慢出)kCAMediaTimingFunctionEaseInEaseOut
(慢进慢出,中间加速)kCAMediaTimingFunctionDefault
(默认),当然也可通过自定义创建CAMediaTimingFunction
。
注意:KCAMediaTimingFuncationDefault相比KCAMediaTimingFuncationEaseInEaseOut的加速和减速过程稍微有些慢,两者区别很难察觉;可能苹果也觉得它更适合用于隐式动画,就作为了隐式动画的默认效果;但是创建显式的CAAnimation时,KCAMediaTimingFuncationLinear才是默认效果而非KCAMediaTimingFuncationDefault;removedOnCompletion
-> 是否让图层保持显示动画执行后的状态,默认为YES
,也就是动画执行完毕后从涂层上移除,恢复到执行前的状态,如果设置为NO
,并且设置fillMode
为kCAFillModeForwards
,则保持动画执行后的状态。delegate -> 代理。
CAAnimation
的delegate
代理方法如下几个:
//动画开始时执行的回调
- (void)animationDidStart:(CAAnimation *)anim;
//动画结束后执行的回调
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
removedOnCompletion
属性默认为YES
,表示动画完成后就会从图层上移除,图层也会恢复到动画执行前的状态;当其修改为NO
时,那么图层将会保持动画结束后的状态,此时的fillMode
属性也将生效;
另外,removedOnCompletion
设置为NO
时,直到我们手动移除动画,否则动画将不会自动释放;所以通常我们此时会给动画添加一个非空的键,这样可以在不需要动画的时候把它从图层上移除;
3:CAPropertyAnimation
属性动画
CAPropertyAnimation
是一个抽象类,不可直接使用。不能直接用于实现CALayer动画操作,但是它的类定义中增加用于设置CALayer可被实现动画的属性keyPath。
方法
:+ (instancetype)animationWithKeyPath:(NSString *)path;
该方法仅需要一个参数,该参数只是一个字符串的值,指定CALayer的动画属性名,该设置属性动画控制CALayer的哪个动画属性持续的改变。
属性
:具体如下:
keyPath
-> CALayer的某个属性名,并通过这个属性的值进行修改,达到相应的动画效果。additive
-> 属性动画是否以当前动画效果为基础,默认为NO。cumulative
-> 指定动画是否为累加效果,默认为NO。valueFunction
-> 此属性配合CALayer的transform属性使用。
CABasicAnimation基础动画,通过keyPath对应属性进行控制,需要设置fromValue以及toValue。添加属性如下:fromValue
-> keyPath相应属性的初始值。toValue
-> keyPath相应属性的结束值。byValue
-> 在不设置toValue时,toValue = fromValue + byValue,也就是在当前的位置上增加多少。affineTransform
:该属性值指定一个(- (CGAffineTransform)affineTransform;)CGAffineTransform对象(变换矩阵),该对象代表CALayer执行X,Y两个维度(也就是平面)上的旋转,缩放,位移,斜切,镜像等变换矩阵。transform
: 该属性值指定一个CATransform3D对象,该对象代表对CALayer执行X,Y,Z三个维度(三维空间)中的旋转,缩放,位移,斜切,镜像等变换矩阵。很明显如果只是对CALayer进行平面上的变换,指定普通的affineTransform属性即可,如果要对CALayer执行三维空间的变化,则需要指定transform属性。
CATransform3D:就是下面的结构体
struct CATransform3D
{
CGFloat m11, m12, m13, m14;
CGFloat m21, m22, m23, m24;
CGFloat m31, m32, m33, m34;
CGFloat m41, m42, m43, m44;
};
其中(m11, m12, m13,m21, m22, m23,m31, m32, m33)将会组成变换矩阵,m14,m24,m34,m44只是占位符,通常m14,m24,m34会设置为0.m14设置为1.假如变换前的店坐标为(x,y,z),与该矩阵相乘后得到变换后该点的坐标。按矩阵相乘算法:
[x,y,z].(m11,m12,m13
m21,m22,m23
m31,m32,m33)=(xm11+ym21+zm31 xm12+ym22+zm32 xm13+ym23+z*m33)
上面公司计算出来的坐标还要加上tx,ty,tz这3个X,Y,Z方向的偏移量。因此对于点(x,y,z)经过CATransform3D变换后,该点的实际坐标为(xm11+ym21+zm31+tx xm12+ym22+zm32+ty xm13+ym23+z*m33+tz).
一般来说可以使用 Core Animation提供的如下函数来创建三维变换矩阵。
CATransform3DIsIdentity(CATransform3D t)
:判断t矩阵是否为单位矩阵
CATransform3DEqualToTransform(CATransform3D a, CATransform3D b)
:判断两个变换矩阵是否相等
CATransform3DMakeTranslation(CGFloat tx, CGFloat ty, CGFloat tz)
:创建在x方向上移动tx,在y方向上移动ty,在z方向上移动tz的变换矩阵。
CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz)
:创建在x方向上缩放tx,在y方向上缩放ty,在z方向上缩放tz的变换矩阵。
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
:创建基于指定旋转轴旋转angle弧度的变换,其中x,y,z用于确定旋转轴的方向。比如 (1,0,0)指定旋转轴为x轴,(1,1,0)指定以x,y轴夹角的中线为旋转轴。
CATransform3DTranslate(CATransform3D t, CGFloat tx, CGFloat ty, CGFloat tz)
:以已有t变换矩阵为基础进行位移变换。
CATransform3DScale(CATransform3D t, CGFloat sx, CGFloat sy, CGFloat sz)
: 以已有t变换矩阵为基础进行缩放变换。
CATransform3DRotate(CATransform3D t, CGFloat angle, CGFloat x, CGFloat y, CGFloat z)
:以已有t变换矩阵为基础进行旋转变换。
CATransform3DConcat(CATransform3D a, CATransform3D b)
:对a变换矩阵进行累加
CATransform3DInvert(CATransform3D t)
:对已有的t变换矩阵执行反转。
CATransform3DMakeAffineTransform(CGAffineTransform m)
:将CGAffineTransform矩阵包装成为CATransform3D变换矩阵,该CATransform3D也只有x,y维度变换。
CATransform3DIsAffine(CATransform3D t)
:如果t变换矩阵只有一个CGAffineTransform矩阵,则改函数返回YES.
CATransform3DGetAffineTransform(CATransform3D t)
:获取t变换矩阵所包含的CGAffineTransform变换矩阵。
CAPropertyAnimation
使用
1:利用+ (instancetype)animationWithKeyPath:(NSString *)keyPath
类方法创建属性动画
2:如果使用基本属性动画CABasicAnimation
,则可指定fromValue,toValue两个属性值,其中fromValue指定动画属性开始时的属性值,toValue指定动画属性结束的属性值.如果使用CAKeyframeAnimation属性动画,则指定values属性值,该属性值是一个 NSArray属性,其中第一个元素是属性的开始值.依次变化.区别在于CABasicAnimation只能够指定开始值和结束值.而CAKeyframeAnimation则可以指定动画属性的多个值.CAKeyframeAnimation还可以通过控制路劲来控制移动.
3:调用CALayer
的- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key
添加动画即可.key用于多图层进行分辨.单图层看不用设置.
举例:demo 实现基本的位移 缩放 旋转 组合动画
#import <QuartzCore/QuartzCore.h>
#import "FKViewController.h"
@implementation FKViewController
CALayer *imageLayer;
- (void)viewDidLoad
{
[superviewDidLoad];
// 创建一个CALayer对象
imageLayer = [CALayerlayer];
//设置该CALayer的边框、大小、位置等属性
imageLayer.cornerRadius =6;
imageLayer.borderWidth =1;
imageLayer.borderColor = [UIColorblackColor].CGColor;
imageLayer.masksToBounds =YES;
imageLayer.frame =CGRectMake(30,30, 100, 135);
// 设置该imageLayer显示的图片
imageLayer.contents = (id)[[UIImageimageNamed:@"android"]CGImage];
[self.view.layeraddSublayer:imageLayer];
NSArray* bnTitleArray = [NSArrayarrayWithObjects:@"位移"
, @"旋转" ,@"缩放" ,@"动画组" ,nil];
//获取屏幕的内部高度
CGFloat totalHeight = [UIScreenmainScreen].bounds.size.height;
NSMutableArray* bnArray = [[NSMutableArrayalloc] init];
//采用循环创建4个按钮
for(int i =0 ; i < 4 ; I++)
{
UIButton* bn = [UIButtonbuttonWithType:UIButtonTypeRoundedRect];
bn.frame =CGRectMake(5 + i *80, totalHeight - 45 - 20 , 70 ,35);
[bnsetTitle:[bnTitleArray objectAtIndex:i]
forState:UIControlStateNormal];
[bnArrayaddObject:bn];
[self.viewaddSubview:bn];
}
//为4个按钮绑定不同的事件处理方法
[[bnArray objectAtIndex:0]addTarget:selfaction:@selector(move:)
forControlEvents:UIControlEventTouchUpInside];
[[bnArray objectAtIndex:1]addTarget:selfaction:@selector(rotate:)
forControlEvents:UIControlEventTouchUpInside];
[[bnArray objectAtIndex:2]addTarget:selfaction:@selector(scale:)
forControlEvents:UIControlEventTouchUpInside];
[[bnArray objectAtIndex:3]addTarget:selfaction:@selector(group:)
forControlEvents:UIControlEventTouchUpInside];
}
-(void) move:(id)sender
{
CGPoint fromPoint =imageLayer.position;
CGPoint toPoint =CGPointMake(fromPoint.x +80 , fromPoint.y);
// 创建不断改变CALayer的position属性的属性动画
CABasicAnimation* anim = [CABasicAnimation
animationWithKeyPath:@"position"];
//设置动画开始的属性值
anim.fromValue = [NSValuevalueWithCGPoint:fromPoint];
//设置动画结束的属性值
anim.toValue = [NSValuevalueWithCGPoint:toPoint];
anim.duration =0.5;
imageLayer.position = toPoint;//设置移动后图片的位置。
anim.removedOnCompletion =YES;
// 为imageLayer添加动画
[imageLayeraddAnimation:anim forKey:nil];
}
-(void) rotate:(id)sender
{
// 创建不断改变CALayer的transform属性的属性动画
CABasicAnimation* anim = [CABasicAnimationanimationWithKeyPath:@"transform"];
CATransform3D fromValue =imageLayer.transform;
//设置动画开始的属性值
anim.fromValue = [NSValuevalueWithCATransform3D:fromValue];
// 绕X轴旋转180度
CATransform3D toValue =CATransform3DRotate(fromValue, M_PI , 1 , 0 , 0);
// 绕Y轴旋转180度
// CATransform3D toValue = CATransform3DRotate(fromValue, M_PI , 0 , 1 , 0);
// // 绕Z轴旋转180度
// CATransform3D toValue = CATransform3DRotate(fromValue, M_PI , 0 , 0 , 1);
//设置动画结束的属性值
anim.toValue = [NSValuevalueWithCATransform3D:toValue];
anim.duration =0.5;
imageLayer.transform = toValue;
anim.removedOnCompletion =YES;
// 为imageLayer添加动画
[imageLayeraddAnimation:anim forKey:nil];
}
-(void) scale:(id)sender
{
// 创建不断改变CALayer的transform属性的属性动画
CAKeyframeAnimation* anim = [CAKeyframeAnimation
animationWithKeyPath:@"transform"];
// 设置CAKeyframeAnimation控制transform属性依次经过的属性值
anim.values = [NSArrayarrayWithObjects:
[NSValuevalueWithCATransform3D:imageLayer.transform],
[NSValuevalueWithCATransform3D:CATransform3DScale
(imageLayer.transform ,0.2, 0.2, 1)],
[NSValuevalueWithCATransform3D:CATransform3DScale
(imageLayer.transform,2, 2 , 1)],
[NSValuevalueWithCATransform3D:imageLayer.transform],nil];
anim.duration =5;
anim.removedOnCompletion =YES;
// 为imageLayer添加动画
[imageLayeraddAnimation:anim forKey:nil];
}
-(void) group:(id)sender
{
CGPoint fromPoint =imageLayer.position;
CGPoint toPoint =CGPointMake(280 , fromPoint.y +300);
// 创建不断改变CALayer的position属性的属性动画
CABasicAnimation* moveAnim = [CABasicAnimation
animationWithKeyPath:@"position"];
//设置动画开始的属性值
moveAnim.fromValue = [NSValuevalueWithCGPoint:fromPoint];
//设置动画结束的属性值
moveAnim.toValue = [NSValuevalueWithCGPoint:toPoint];
moveAnim.removedOnCompletion =YES;
// 创建不断改变CALayer的transform属性的属性动画
CABasicAnimation* transformAnim = [CABasicAnimation
animationWithKeyPath:@"transform"];
CATransform3D fromValue =imageLayer.transform;
//设置动画开始的属性值
transformAnim.fromValue = [NSValuevalueWithCATransform3D: fromValue];
//创建缩放为X、Y两个方向上缩放为0.5的变换矩阵
CATransform3D scaleValue =CATransform3DScale(fromValue , 0.5 , 0.5, 1);
//绕Z轴旋转180度的变换矩阵
CATransform3D rotateValue =CATransform3DRotate(fromValue, M_PI , 0 , 0 , 1);
//计算两个变换矩阵的和
CATransform3D toValue =CATransform3DConcat(scaleValue, rotateValue);
//设置动画技术的属性值
transformAnim.toValue = [NSValuevalueWithCATransform3D:toValue];
//动画效果累加
transformAnim.cumulative =YES;
//动画重复执行2次,旋转360度
transformAnim.repeatCount =2;
transformAnim.duration =3;
//位移、缩放、旋转组合起来执行
CAAnimationGroup *animGroup = [CAAnimationGroupanimation];
animGroup.animations = [NSArrayarrayWithObjects:moveAnim
, transformAnim ,nil];
animGroup.duration =6;
// 为imageLayer添加动画
[imageLayeraddAnimation:animGroup forKey:nil];
}
@end
举例:控制移动的路径
对于CAKeyframeAnimation而言,它除了可通过values属性指定动画过程中的多个值之外,还可以通过path属性指定CALayer的移动路径,改属性就是CGPathRef,通过这种方式即可控制CALayer按我们指定的轨迹移动,从而执行更加细致的动画。
#import <QuartzCore/QuartzCore.h>
#import "FKViewController.h"
@interface FKViewController ()
@end
@implementation FKViewController
CALayer* fishLayer;
NSInteger fishFrame;
NSTimer* timer;
// 定义NSMutableArray装鱼的10个动画帧
NSMutableArray* fishFrameArray;
- (void)viewDidLoad
{
[superviewDidLoad];
// 创建CALayer作为背景
CALayer* bg = [CALayerlayer];
//设置背景图片
bg.contents = (id)[UIImageimageNamed:@"bg.jpg"].CGImage;
bg.contentsGravity =kCAGravityCenter;
bg.frame =CGRectMake(0,0, 320, 480);
[self.view.layeraddSublayer:bg];
fishFrameArray = [[NSMutableArrayalloc] init];
// 初始化鱼的10个动画帧,并添加到fishFrameArray集合中
for(int i =0 ; i < 10 ; I++)
{
[fishFrameArrayaddObject:[UIImageimageNamed:
[NSStringstringWithFormat:@"fish%d.png" , i]]];//多张连串的图片
}
//创建定时器控制小鱼的动画帧的改变。
timer = [NSTimerscheduledTimerWithTimeInterval:0.15target:self
selector:@selector(change)userInfo:nilrepeats:YES];
// 创建CALayer
fishLayer = [CALayerlayer];
//设置CALayer显示内容的对齐、缩放模式(不缩放,直接显示在中间)
fishLayer.contentsGravity =kCAGravityCenter;
// 设置fishLayer的大小
fishLayer.frame =CGRectMake(128,6, 90, 40);
[self.view.layeraddSublayer:fishLayer];
//创建一个按钮,通过该按钮触发小鱼的游动
UIButton* bn = [UIButtonbuttonWithType:UIButtonTypeRoundedRect];
bn.frame =CGRectMake(10 ,10 , 60 , 35);
[bn setTitle:@"开始"forState:UIControlStateNormal];
[self.viewaddSubview:bn];
//用户点击按钮时,激发start:方法
[bnaddTarget:selfaction:@selector(start:)
forControlEvents:UIControlEventTouchUpInside];
}
-(void) start:(id)sender
{
// 创建对CALayer的position属性执行控制的属性动画
CAKeyframeAnimation* anim = [CAKeyframeAnimation
animationWithKeyPath:@"position"];
// 创建路径
CGMutablePathRef movePath =CGPathCreateMutable();
//添加一条圆形的路径
CGPathAddArc(movePath,nil, 170, 175, 150, -M_PI /2, M_PI * 3 / 2, YES);
//设置anim动画的移动路径
anim.path = movePath;
// 创建对CALayer的transform属性执行控制的属性动画
CAKeyframeAnimation* anim2 = [CAKeyframeAnimation
animationWithKeyPath:@"transform"];
//指定关键帧动画的3个关键值:分别是不旋转,旋转180度,旋转360度
anim2.values = [NSArrayarrayWithObjects:
[NSValuevalueWithCATransform3D:CATransform3DIdentity],
[NSValuevalueWithCATransform3D:
CATransform3DMakeRotation(M_PI , 0, 0,1)],
[NSValuevalueWithCATransform3D:
CATransform3DMakeRotation(2 * M_PI , 0,0, 1)]
,nil];
//使用动画组来组合2个动画
CAAnimationGroup *animGroup = [CAAnimationGroupanimation];
animGroup.animations = [NSArrayarrayWithObjects:anim, anim2, nil];
//指定动画重复10次
animGroup.repeatCount =10;
animGroup.duration =24;
// 为fishLayer添加动画
[fishLayeraddAnimation:animGroup forKey:@"move"];
}
// 该方法由定时器触发,不断更改fishLayer显示的动画帧
- (void) change
{
fishLayer.contents = (id)[[fishFrameArray
objectAtIndex:fishFrame++ %10] CGImage];
}
@end
4:CAAnimationGroup 动画组,方便对于多动画的统一控制管理。
能将多个动画组合在一起,如平移、缩放、旋转等效果组合在一起做出更炫酷的的效果。它只有一个特有的属性。
animations
-> 所有动画效果元素的数组。
举例1:
@property(nullable, copy) NSArray<CAAnimation *> *animations;
NSLog(@"动画执行前redView.frame:%@", NSStringFromCGRect(self.redView.frame));
//平移
CABasicAnimation *transition = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
transition.toValue = @(300);
//旋转
CABasicAnimation *rotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
rotation.toValue = @(M_PI);
//缩放
CABasicAnimation *scale = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
scale.toValue = @(0.2);
//添加组动画
CAAnimationGroup *group = [CAAnimationGroup animation];
//注意这里动画的效果 要设置成group的
group.duration = 2.0;
group.animations = @[rotation, scale, transition];
group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;
[self.redView.layer addAnimation:group forKey:nil];
举例2:
@interface TestAnimationGroupVC ()
@property (nonatomic,strong) UIView *colorView;
@property (nonatomic,strong) UIBezierPath *bezierPath;
@end
@implementation TestAnimationGroupVC
- (void)viewDidLoad {
[super viewDidLoad];
//创建显示颜色的图层
self.colorView = [UIView new];
self.colorView.frame = CGRectMake(0, 0, 60, 60);
self.colorView.center = CGPointMake(50, 200);
self.colorView.backgroundColor = [UIColor orangeColor];
[self.view addSubview:self.colorView];
//创建贝塞尔曲线,即帧动画运动轨迹
self.bezierPath = [[UIBezierPath alloc] init];
[self.bezierPath moveToPoint:CGPointMake(50, 200)];
[self.bezierPath addCurveToPoint:CGPointMake(kDeviceWidth - 50, 200) controlPoint1:CGPointMake(150, 50) controlPoint2:CGPointMake(kDeviceWidth - 150, 250)];
//绘制绘制path,便于观察动画;
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = self.bezierPath.CGPath;
pathLayer.fillColor = [UIColor clearColor].CGColor;
pathLayer.strokeColor = [UIColor redColor].CGColor;
pathLayer.lineWidth = 3.0f;
[self.view.layer addSublayer:pathLayer];
}
- (IBAction)startAnimation:(UIButton *)sender{
//移除可能未执行完的动画,防止多重动画导致异常
[self.colorView.layer removeAnimationForKey:@"groupAnimation"];
//1.创建基础动画:修改背景色为紫色
CABasicAnimation *basicAnimation = [CABasicAnimation animation];
basicAnimation.keyPath = @"backgroundColor";
basicAnimation.toValue = (__bridge id _Nullable)([UIColor purpleColor].CGColor);
//2.创建关键帧动画
CAKeyframeAnimation *keyFrameAnimation = [CAKeyframeAnimation animation];
keyFrameAnimation.keyPath = @"position";
keyFrameAnimation.path = self.bezierPath.CGPath;
keyFrameAnimation.rotationMode = kCAAnimationRotateAuto;
//3.创建组动画:组合基础动画和关键帧动画
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[basicAnimation, keyFrameAnimation];
groupAnimation.duration = 4.0;
[self.colorView.layer addAnimation:groupAnimation forKey:@"groupAnimation"];
}
动画组的效果如下:
5:CABasicAnimation
基础动画
CABasicAnimation
即基础动画,在指定可动画属性后,动画会按照预定的参数持续一定时间由初始值变换为终点值。其实,CABasicAnimation就相当于只有开始和结束两个帧的特殊关键帧动画(后续会详解);
属性 属性说明
fromValue
-> 起始值toValue
-> 结束值byValue
-> keyPath属性的变化值
下面的示例使用CABasicAnimation
实现了修改颜色图层colorLayer的背景色
为随机颜色的动画,具体的代码如下:
@interface TestBacicAnimation1VC ()<CAAnimationDelegate>
@property (nonatomic,strong) CALayer *colorLayer;
@end
@implementation TestBacicAnimation1VC
- (void)viewDidLoad {
[super viewDidLoad];
//创建显示颜色的图层,添加于视图控制器的View上
CALayer *colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(50, 50, 100, 100);
colorLayer.backgroundColor = [UIColor redColor].CGColor;
self.colorLayer = colorLayer;
[self.view.layer addSublayer:colorLayer];
}
- (IBAction)changeColor:(UIButton *)sender{
//步骤1:创建动画
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"backgroundColor";
//步骤2:设定动画属性
animation.autoreverses = NO;
animation.duration = 0.25;
animation.repeatCount = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.delegate = self;
UIColor *randomColor = [UIColor randomColor]; //自定义获取随机色的方法
animation.toValue = (__bridge id _Nullable)(randomColor.CGColor);
//步骤3:添加动画到图层
[self.colorLayer addAnimation:animation forKey:@"keyPath_backgroundColor"];
}
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{
//禁用隐式动画
[CATransaction begin];
[CATransaction setDisableActions:true];
self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
[CATransaction commit];
}
效果如下:
总结创建动画的两种方式如下:
//方法1:实例化同时指定动画类型
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
//方法2:先实例化,再指定动画类型
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"backgroundColor";
关闭隐式动画
对独立图层(即非UIView的关联图层,类似上述例子中的colorLayer)做更新属性的显式动画,我们需要设置一个事务来禁用图层行为,否则动画会发生两次,一次是因为显式的CABasicAnimation,另一次是因为隐式动画,从而导致我们看到的动画异常。
6:CAKeyframeAnimation
关键帧动画
CAKeyframeAnimation
关键帧动画,同样通过·keyPath·对应属性进行控制,但它可以通过·values·或者·path·进行多个阶段的控制,属性如下:
values
-> 关键帧组成的数组,动画会依次显示其中的每一帧。path
-> 关键帧路径,动画进行的要素,优先级比values高,但是只对CALayer的anchorPoint和position起作用。如果你设置了path,那么values将被忽略keyTimes
-> 每一帧对应的时间,如果不设置,则各关键帧平分设定时间。timingFunctions
-> 每一帧对应的动画节奏。calculationMode
-> 动画的计算模式,系统提供了对应的几种模式。tensionValues
-> 动画张力控制。continuityValues
-> 动画连续性控制。biasValues
-> 动画偏差率控制。rotationMode
-> 动画沿路径旋转方式,系统提供了两种模式。
简单的创建一个带路径的动画效果,比较粗糙,不过事先原理都是一样的
代码如下:
实现帧动画:使用values
从关键帧动画的属性可以看出,我们可以总结出关键帧动画的实现方式实际分为两种:
1.通过values设置关键帧属性值数组;
2.通过path设置关键帧路径,而且此种方式的优先级较高;
这里首先测试第一种方式,实现这样的关键帧动画:创建一个紫色滑块在四个坐标点之间滑动;具体的代码实现如下:
- (void)viewDidLoad {
[super viewDidLoad];
//创建测试帧动画的紫色图层
UIView *purpleView = [UIView new];
purpleView.frame = CGRectMake(0, 0, 50, 50);
purpleView.center = CGPointMake(50, 100);
purpleView.backgroundColor = [UIColor purpleColor];
[self.view addSubview:purpleView];
//步骤1:创建动画
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
//步骤2:设置动画关键帧数据
NSValue *value1 = [NSValue valueWithCGPoint:CGPointMake(50, 100)];
NSValue *value2 = [NSValue valueWithCGPoint:CGPointMake(kDeviceWidth -50, 100)];
NSValue *value3 = [NSValue valueWithCGPoint:CGPointMake(kDeviceWidth -50, kDeviceWidth- 100)];
NSValue *value4 = [NSValue valueWithCGPoint:CGPointMake(50, kDeviceWidth -100)];
NSValue *value5 = [NSValue valueWithCGPoint:CGPointMake(50, 100)];
animation.values = @[value1,value2,value3,value4,value5];
//步骤3:设定动画属性
animation.repeatCount = MAXFLOAT; //重复执行
animation.autoreverses = NO;
animation.removedOnCompletion = NO;
animation.duration = 4;
//animation.keyTimes = @[@(0), @(1 / 10.0), @(5 / 10.0), @(9 / 10.0), @(1) ];
animation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[purpleView.layer addAnimation:animation forKey:nil];
}
关键帧动画效果如下:
实现关键帧动画:使用path
现在,我们测试CAKeyframeAnimation使用path实现这样一个动画:一架飞机沿着一个简单的曲线运动飞行;具体的操作包括以下几个步骤:
1.使用UIKit提供的UIBezierPath类创建贝塞尔曲线,作为飞机飞行的路线轨迹;
2.使用CAShapeLayer在屏幕上绘制曲线(此步骤对于动画不是必须的,只是为了动画看起来更直观);
3.创建用于显示飞机的视图,将其设置在贝塞尔曲线的初始位置;
4.创建并执行关键帧动画,实现飞机飞行的曲线动画;
- (void)viewDidLoad {
[super viewDidLoad];
//1.创建三次贝塞尔曲线(一种使用起始点,结束点和另外两个控制点定义的曲线);
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:CGPointMake(50, 200)];
[bezierPath addCurveToPoint:CGPointMake(kDeviceWidth - 50, 200) controlPoint1:CGPointMake(150, 50) controlPoint2:CGPointMake(kDeviceWidth - 150, 250)];
//2.绘制飞行路线
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = bezierPath.CGPath;
pathLayer.fillColor = [UIColor clearColor].CGColor;
pathLayer.strokeColor = [UIColor redColor].CGColor;
pathLayer.lineWidth = 3.0f;
[self.view.layer addSublayer:pathLayer];
//3.创建显示飞机的视图
UIImageView *airPlaneImgView = [[UIImageView alloc] init];
airPlaneImgView.frame = CGRectMake(0, 0, 50, 50);
airPlaneImgView.center = CGPointMake(50, 200);
airPlaneImgView.image = [UIImage imageNamed:@"airplane"];
[self.view addSubview:airPlaneImgView];
//4.设置关键帧动画
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
animation.duration = 5.0;
animation.path = bezierPath.CGPath;
animation.rotationMode = kCAAnimationRotateAuto; //设置根据曲线的切线自动旋转,让动画更加真实
[airPlaneImgView.layer addAnimation:animation forKey:nil];
}
关键帧动画效果图如下:
7:CATransition
转场动画,系统提供了很多酷炫效果。属性如下:
type
-> 转场动画类型。
kCATransitionFade:交叉淡化过渡
kCATransitionPush:新视图吧旧视图
kCATransitionMoveIn:新视图移到旧视图上面
kCATransitionReveal:将旧视图移开,显示下面的视图
用字符串表示:
pageCurl:向上翻页效果
pageUnCurl:向下翻页效果
cube:立方体翻滚效果
oglFlip:上下左右翻转效果
rippleEffect:波纹
suckEffect:吮吸
flipFromLeft:左翻转
flipFromRight:右翻转
suckEffect:收缩效果,如一块布被抽走
rippleEffect:水滴效果
cameraIrisHollowOpen:相机镜头打开效果
cameraIrisHollowClose:相机镜头关闭效果subtype
-> 转场动画方向。
kCATransitionFromRight 从右向左,kCATransitionFromTop 从上向下,kCATransitionFromLeft 从左向右,kCATransitionFromBottom 从下向上startProgress
-> 动画起点进度(整体的百分比)。endProgress
-> 动画终点进度(整体的百分比)。filter
-> 自定义转场。duration
:动画执行时间timingFunction
: 动画的运动轨迹,用于变化起点和终点之间的插值计算,形象点说它决定了动画运行的节奏,比如是均匀变化(相同时间变化量相同)还是先快后慢,先慢后快还是先慢再快再慢
kCAMediaTimingFunctionLinear 线性,即匀速
kCAMediaTimingFunctionEaseIn 先慢后快
kCAMediaTimingFunctionEaseOut 先快后慢
kCAMediaTimingFunctionEaseInEaseOut 先慢后快再慢
kCAMediaTimingFunctionDefault 实际效果是动画中间比较快
示例1:
//转场动画
-(void)transitionAnimation{
CATransition *transtion = [CATransition animation];
transtion.type = @"rippleEffect";
transtion.subtype = kCATransitionFromLeft;//kCATransitionFromLeft kCATransitionFromRight
transtion.duration = 1;
_transtionIndex++;
if (_transtionIndex > 4) {
_transtionIndex = 1;
}
_aniLayer.contents = (id)[UIImage imageNamed:[NSString stringWithFormat:@"%@.jpg",@(_transtionIndex)]].CGImage;
[_aniLayer addAnimation:transtion forKey:@"transtion"];
}
示例2:
//创建动画
CATransition *transition = [[CATransition alloc]init];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
transition.type = kCATransitionReveal;
transition.subtype = kCATransitionFromBottom;
UULoginUserViewController *login = [[UULoginUserViewController alloc] init];
UUNavigationController *nav = [[UUNavigationController alloc] initWithRootViewController:login];
self.window.rootViewController = nav;
[self.window.layer addAnimation:transition forKey:@"animation"];
示例3:
@interface TestTransition1VC ()
@property (nonatomic,strong) UIImageView *imageView;
@property (nonatomic,strong) NSArray *images;
@property (nonatomic, copy) NSString *type;
@property (nonatomic, copy) NSString *subtype;
@end
@implementation TestTransition1VC
- (void)viewDidLoad {
[super viewDidLoad];
self.images = @[[UIImage imageNamed:@"tree_spring"],
[UIImage imageNamed:@"tree_summer"],
[UIImage imageNamed:@"tree_autumn"],
[UIImage imageNamed:@"tree_winter"]];
self.type = kCATransitionFade;
self.subtype = kCATransitionFromRight;
}
- (void)perforomTransitionAnimation{
CATransition *transition = [[CATransition alloc] init];
transition.type = _type;
transition.subtype = _subtype;
transition.duration = 0.5;
[self.imageView.layer addAnimation:transition forKey:nil];
UIImage *currentImage = self.imageView.image;
NSUInteger index = [self.images indexOfObject:currentImage];
index = (index + 1) % self.images.count;
self.imageView.image = self.images[index];
}
过渡动画的效果如下:
自定义过渡动画
过渡动画的过程就是对原始图层外观截图,然后添加一段动画,平滑过渡到图层改变之后的那个截图效果。如果我们知道如何对图层截图,我们就可以使用属性动画来自定义CATransition动画了。
CALayer有一个-renderInContenxt:方法,通过它可以将图层绘制到Core Graphics的上下文中捕获当前内容的图片;所以现在我们尝试这样的实现:对当前视图控制器View进行截图,然后在改变其背景色的时候对截图快速旋转并且淡出,以达到一种过渡的效果;具体的代码示例如下:
- (void)performAnimation{
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();
UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];
coverView.frame = self.view.bounds;
[self.view addSubview:coverView];
//使用自定义方法得到随机颜色(切换后的颜色)
UIColor *randomColor = [UIColor randomColor];
self.view.backgroundColor = randomColor;
//使用UIView动画方法来代替属性动画(为了简化代码步骤)
[UIView animateWithDuration:1 animations:^{
CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 0.01);
transform = CGAffineTransformRotate(transform, M_PI_2);
coverView.transform = transform;
coverView.alpha = 0.0;
} completion:^(BOOL finished) {
[coverView removeFromSuperview];
}];
}
自定义过渡动画的效果如下:
7:CASpringAnimation
弹簧动画
CASpringAnimation
弹簧动画,带有初始速度以及阻尼指数等物理参数的属性动画。我们可以把它看成在不绝对光滑的地面上,一个弹簧拴着别小球,那么我们可以这么理解他的属性(物理知识请问一下牛顿大叔):
mass
-> 质量越大,弹簧拉伸和压缩的幅度越大,动画的速度变慢,并且波动幅度变大,影响图层运动时的弹簧惯性stiffness
-> 弹簧的劲度系数。刚度系数越大,形变产生的力就越大,运动越快damping
-> 阻尼系数,地面的摩擦力。阻尼系数越大,停止越快initialVelocity
-> 初始速度,相当于给小球一个初始速度(可正可负,方向不同),初始速率,动画视图的初始速度大小
速率为正数时,速度方向与运动方向一致,速率为负数时,速度方向与运动方向相反settlingDuration
-> 结算时间 返回弹簧动画到停止时的估算时间,根据当前的动画参数估算 通常弹簧动画的时间使用结算时间比较准确
示例:
self.animationView = [[UIView alloc]init];
self.animationView.frame = CGRectMake(0, kHeight/2-50, 50, 50);
self.animationView.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.animationView];
//弹簧效果
CASpringAnimation *spring = [CASpringAnimation animationWithKeyPath:@"position.y"];
spring.fromValue = @150;
spring.toValue = @100;
//阻尼系数
spring.damping = 0.1;
//刚度系数: (劲度系数 / 弹性系数): 系数越大,形变的产生的力越大, 运动越快
spring.stiffness = 10;
//质量: 影响图层运动时候的惯性, 质量越大弹簧拉伸和压缩的幅度越大 (动画的幅度,波动变大)
spring.mass = 1;
//初识速率: 动画视图的初识速度大小
//速率为正时候, 速度方向与运动方向一致, 否则相仿.
spring.initialVelocity = 1;
// settlingDuration 结算时间,预估弹簧动画到停住的时间的估算, 根据当前动画的各个参数估算, 通常弹簧动画的估算时间使用结算时间比较准确
spring.duration = spring.settlingDuration;
[self.animationView.layer addAnimation:spring forKey:@"springAnimation"];
最后
看完这些相信你对 iOS 中的动画有了一个详细的了解, 其实单个动画都是比较简单的, 而复杂的动画其实都是由一个个简单的动画组装而成的,所以遇到比较难得动画需求, 我们只要充分组装不同的动画,就能实现出满意的效果.
好记性不如烂笔头, 光说不练假把戏, 建议大家结合我的代码, 自己边看边练习, 这样才能记得牢, 才能转换成自己的知识.
此处有个demo,可以去看看别人写的东西。
github: https://github.com/YTiOSer/YTAnimation