iOS核心动画详解2

上一篇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,并且设置fillModekCAFillModeForwards,则保持动画执行后的状态。
  • delegate -> 代理。

CAAnimationdelegate代理方法如下几个:

//动画开始时执行的回调
- (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];
image.gif

举例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"];
}

动画组的效果如下:


image.gif
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];
}

效果如下:

image.gif

总结创建动画的两种方式如下:

//方法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];
}

关键帧动画效果如下:


image.gif

实现关键帧动画:使用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];
}

关键帧动画效果图如下:


image.gif
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];
}

过渡动画的效果如下:


image.gif

自定义过渡动画

过渡动画的过程就是对原始图层外观截图,然后添加一段动画,平滑过渡到图层改变之后的那个截图效果。如果我们知道如何对图层截图,我们就可以使用属性动画来自定义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];
    }];
 }

自定义过渡动画的效果如下:


image.gif
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

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

推荐阅读更多精彩内容

  • iOS核心动画 核心动画框架 CoreAnimation框架是基于OpenGL与CoreGraphics图像处理框...
    小白PK大牛阅读 363评论 0 0
  • 文章目录 iOS动画 UIView动画2.1 设置UIView动画的两种语法形式2.2 设置属性形变动画的两种类型...
    luonaerduo阅读 785评论 0 2
  • 最近买了几本书,接下来会陆续写写笔记,把书中干货分享出来 笔记主要总结了书中讲了什么内容,希望读者读完后遇到相应的...
    搬砖人666阅读 1,952评论 0 6
  • 基础 核心动画是 iOS 和 MacOS 上的图形渲染和动画基础结构,用于为应用的视图和其他视觉元素设置动画。 核...
    davon阅读 1,884评论 0 8
  • 本文将分为四个部分介绍核心动画: 第一部分将介绍核心动画的基本概念。 第二部分将介绍动画实现原理。 第三部分将介绍...
    曲年阅读 3,163评论 1 9