iOS DeveloperiOS零碎知识首页投稿(暂停使用,暂停投稿)

QuartzCore之CoreAnimation核心动画介绍

2016-09-21  本文已影响188人  lltree

1. 简介

  • ** **Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍。也就是说,使用少量的代码就可以实现非常强大的功能。
[该图片来源](http://www.jianshu.com/p/fc9be72adc9f)

2. 核心动画类的层次结构

核心动画位于CAAnimation.h中,该文件提供了以下接口及类层级结构


CAAnimation类继承关系.jpeg

如上图所示,CoreAnimation所使用类如下:

3. CAMediaTiming协议

CAMediaTiming协议共有8个属性,每个属性简单介绍如下

属性 说明
duration 动画持续时间
beginTime 动画相对一个对象开始的时间,起到延时执行的目的
speed 动画执行速度
timeOffset 动画执行偏移
repeatCount 动画执行重复次数
repeatDuration 动画执行时间
autoreverses 动画结束后是否反向执行
fillMode 决定当前对象在非active时间段的行为

3.1. duration

代码如下:

#import "ViewController.h"

#define kScreenW ([UIScreen mainScreen].bounds.size.width)
#define kScreenH ([UIScreen mainScreen].bounds.size.height)

@interface ViewController ()

@property (nonatomic,weak) CALayer *redLayer;
@property (nonatomic,weak) CALayer *blueLayer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
 
    //1  添加一个红色的Layer
    CALayer *redLayer = [CALayer layer];
    self.redLayer = redLayer;
    redLayer.backgroundColor = [UIColor redColor].CGColor;
    redLayer.frame = CGRectMake(0, 0, kScreenW/2.0, 50);
    [self.view.layer addSublayer:redLayer];
    
    //1  添加一个蓝色的Layer
    CALayer *blueLayer =[CALayer layer];
    self.blueLayer = blueLayer;
    blueLayer.backgroundColor =[UIColor blueColor].CGColor;
    blueLayer.frame = CGRectMake(kScreenW/2.0, 0, kScreenW/2.0, 50);
    [self.view.layer addSublayer:blueLayer];
    
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
  
    //给红色Layer添加动画
    CABasicAnimation *redLayerBasicAnimation = [CABasicAnimation animation];
    redLayerBasicAnimation.keyPath=@"position";
    redLayerBasicAnimation.fromValue=[NSValue valueWithCGPoint:self.redLayer.position];
    redLayerBasicAnimation.toValue=[NSValue valueWithCGPoint:CGPointMake(self.redLayer.position.x, kScreenH)];
    
    //设置动画持续时间
    redLayerBasicAnimation.duration = 10;

    [self.redLayer addAnimation:redLayerBasicAnimation forKey:nil];
    
    
    //给蓝色Layer添加动画
    CABasicAnimation *blueLayerBasicAnimation = [CABasicAnimation animation];
    blueLayerBasicAnimation.keyPath=@"position";
    blueLayerBasicAnimation.fromValue=[NSValue valueWithCGPoint:self.blueLayer.position];
    blueLayerBasicAnimation.toValue=[NSValue valueWithCGPoint:CGPointMake(self.blueLayer.position.x, kScreenH)];
   
    //设置动画持续时间
    blueLayerBasicAnimation.duration = 5;

    [self.blueLayer addAnimation:blueLayerBasicAnimation forKey:nil];
}
@end

上述代码在视图上添加两个动画,其中一个动画持续10s,另一个动画持续5秒,竖向位移为屏幕高
执行效果如下


动画持续时间左10右5.gif

3.2. beginTime

beginTime为相对时间系开始的时间,可以认为是起到延时左右,设置右侧蓝色Layer相对当前时间系延时5s时间执行

    //相对于当前时间系延后5秒,起到延时作用
    blueLayerBasicAnimation.beginTime = CACurrentMediaTime()+5;

执行效果显示蓝色相对红色延时5秒后执行,且同时到达底


延时5秒执行.gif

3.3. speed

speed为相对一个时间系,本Layer时间流逝速度,默认情况为1,设置蓝色Layer时间流逝速度为0.5倍则在不设置时间偏移的情况下两者同时到达低端,效果如下:

蓝色Layer0.5倍流逝速度.gif

3.4. timeOffset

该属性主要标记一个动画从何处执行,该属性不存在延迟作用。例如一个动画正常执行如下顺序:

  A   -->  B  -->  C  -->  D   -->  A

此时如果设置 timeOffset 为B状态,则该动画开始执行状态为

  B  -->  C  -->  D   -->  A  -->  B  

设置红色动画持续时间10s,蓝色动画持续时间5秒,设置蓝色动画的timeOffset为2.5秒

  //相对于当前时间系延后5秒,起到延时作用
    //blueLayerBasicAnimation.beginTime = CACurrentMediaTime()+5;
    //设置时间流逝速度
    //blueLayerBasicAnimation.speed = 0.5;
    //设置动画偏移时间
    blueLayerBasicAnimation.timeOffset =CACurrentMediaTime()+2.5;

运行效果如下:


设置动画偏移时间.gif

3.5. repeatCount

repeatCount该属性主要记录动画重复持续次数,设定红色Layer动画持续时间为10s,蓝色为5s,新添加蓝色Layer重复次数为2次,代码如下:

blueLayerBasicAnimation.duration = 5;
    //相对于当前时间系延后5秒,起到延时作用
    //blueLayerBasicAnimation.beginTime = CACurrentMediaTime()+5;
    //设置时间流逝速度
    //blueLayerBasicAnimation.speed = 0.5;
    //设置动画偏移时间
    //blueLayerBasicAnimation.timeOffset =CACurrentMediaTime()+2.5;
    //设置动画重复次数
    blueLayerBasicAnimation.repeatCount = 2;

效果如下:


蓝色重复次数为2次.gif

3.6. repeatDuration

repeatDuration该属性主要记录动画重复持续时间,设定红色Layer动画持续时间为10s,蓝色为5s,新添加蓝色Layer动画持续时间12.5秒,代码如下:

     blueLayerBasicAnimation.duration = 5;
    //相对于当前时间系延后5秒,起到延时作用
    //blueLayerBasicAnimation.beginTime = CACurrentMediaTime()+5;
    //设置时间流逝速度
    //blueLayerBasicAnimation.speed = 0.5;
    //设置动画偏移时间
    //blueLayerBasicAnimation.timeOffset =CACurrentMediaTime()+2.5;
    //设置动画重复次数
    //blueLayerBasicAnimation.repeatCount = 2;
    //设置动画持续时间
    blueLayerBasicAnimation.repeatDuration = 12.5;

运行效果如下:


蓝色持续12.5秒.gif

3.7. autoreverses

该属性主要执行结束动画后是否执行逆行动画过程,默认NO不执行,如果YES则执行,

   blueLayerBasicAnimation.duration = 5;
    //相对于当前时间系延后5秒,起到延时作用
    //blueLayerBasicAnimation.beginTime = CACurrentMediaTime()+5;
    //设置时间流逝速度
    //blueLayerBasicAnimation.speed = 0.5;
    //设置动画偏移时间
    //blueLayerBasicAnimation.timeOffset =CACurrentMediaTime()+2.5;
    //设置动画重复次数
    //blueLayerBasicAnimation.repeatCount = 2;
    //设置动画持续时间
    //blueLayerBasicAnimation.repeatDuration = 12.5;
    //动画
     blueLayerBasicAnimation.autoreverses = YES;

是否执行逆行动画.gif

3.8. fillMode

fillMode决定当前对象在非active时间段的行为.比如动画开始之前,动画结束之后 ,但是该属性要和CAAnimation中的removedOnCompletion属性配合着使用.

/* When true, the animation is removed from the render tree once its
 * active duration has passed. Defaults to YES. */
如果是true,一旦动画的活动期间过了,那么这个动画将被移除,默认值YES
@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;

其中fillMode属性值(要想fillMode有效,最好设置removedOnCompletion=NO)存在四中状态,如下:

/* `fillMode' options. */

CA_EXTERN NSString * const kCAFillModeForwards
    __OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCAFillModeBackwards
    __OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCAFillModeBoth
    __OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);
CA_EXTERN NSString * const kCAFillModeRemoved
    __OSX_AVAILABLE_STARTING (__MAC_10_5, __IPHONE_2_0);

测试kCAFillModeForwards状态代码如下:

    blueLayerBasicAnimation.fillMode = kCAFillModeForwards;
    blueLayerBasicAnimation.removedOnCompletion = NO;

运行效果如下:


kCAFillModeForwards状态测试.gif

4.0 CAAnimation抽象类

CAAnimation是Core Animation所有动画类型的抽象基类。作为一个抽象类,CAAnimation本身并没有做多少工作,它提供了一个计时函数(用于动画的进入进出状态),一个委托(用于反馈动画状态)以及一个removedOnCompletion,用于标识动画是否该在结束后自动释放(默认YES,为了防止内存泄露)。CAAnimation同时实现了一些协议,包括CAMediaTiming。

/* Creates a new animation object. */

+ (instancetype)animation;

/* Animations implement the same property model as defined by CALayer.
 * See CALayer.h for more details. */

+ (nullable id)defaultValueForKey:(NSString *)key;
- (BOOL)shouldArchiveValueForKey:(NSString *)key;

/* A timing function defining the pacing of the animation. Defaults to
 * nil indicating linear pacing. */

@property(nullable, strong) CAMediaTimingFunction *timingFunction;

/* The delegate of the animation. This object is retained for the
 * lifetime of the animation object. Defaults to nil. See below for the
 * supported delegate methods. */

@property(nullable, strong) id delegate;

/* When true, the animation is removed from the render tree once its
 * active duration has passed. Defaults to YES. */

@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;

4.1. +animation方法

该方法为抽象方法,由子类创建实例对象,生成相应的对象类型。例如基本动画、关键帧动画、组动画、转场动画。

4.2. timingFunction属性

摘录参考blog
Timing Function的会被用于变化起点和终点之间的插值计算.形象点说是Timing Function决定了动画运行的节奏(Pacing),比如是均匀变化(线性变化)、先快后慢、先慢后快、还是先慢再快再慢.

时间函数是使用的一段函数来描述的,横座标是时间t取值范围是0.0-1.0,纵座标是变化量x(t)也是取值范围也是0.0-1.0 假设有一个动画,duration是8秒,变化值的起点是a终点是b(假设是透明度),那么在4秒处的值是多少呢? 可以通过计算为 a + x(4/8) * (b-a), 为什么这么计算呢?讲实现的时间映射到单位值的时候4秒相对于总时间8秒就是0.5然后可以得到0.5的时候单位变化量是 x(0.5), x(0.5)/1 = 实际变化量/(b-a), 其中b-a为总变化量,所以实际变化量就是x(0.5) * (b-a) ,最后4秒时的值就是 a + x(0.5) * (b-a),所以计算的本质是映射

Timing Function对应的类是CAMediaTimingFunction,它提供了两种获得时间函数的方式,一种是使用预定义的五种时间函数,一种是通过给点两个控制点得到一个时间函数. 相关的方法为

+ (instancetype)functionWithName:(NSString *)name;

+ (instancetype)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;

- (instancetype)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;

- (void)getControlPointAtIndex:(size_t)idx values:(float[2])ptr;

五种预定义的时间函数名字的常量变量分别为

下图展示了前面四种Timing Function的曲线图,横座标表示时间,纵座标表示变化量,这点需要搞清楚(并不是平面座标系中xy).

时间曲线.jpg
自定义的Timing Function的函数图像就是一条三次贝塞尔曲线Cubic Bezier Curve,贝塞尔曲线的优点就是光滑,用在这里就使得变化显得光滑.一条三次贝塞尔曲线可以由起点终点以及两个控制点决定.
上面的kCAMediaTimingFunctionDefault对应的函数曲线其实就是通过[(0.0,0.0), (0.25,0.1), (0.25,0.1), (1.0,1.0)]这四个点决定的三次贝塞尔曲线,头尾为起点和终点,中间的两个点是控制点. 贝塞尔曲线.jpg

上图中P0是起点,P3是终点,P1和P2是两个控制点

4.3. delegate代理属性

CAAnimation提供代理协议,代理方法提供了动画开始和动画结束方法,通过代理方法执行回调。

@interface NSObject (CAAnimationDelegate)

/* Called when the animation begins its active duration. */

- (void)animationDidStart:(CAAnimation *)anim;

/* Called when the animation either completes its active duration or
 * is removed from the object it is attached to (i.e. the layer). 'flag'
 * is true if the animation reached the end of its active duration
 * without being removed. */

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;

@end

4.4. removedOnCompletion

该方法前边已经介绍,用于移除动画使用

5.0. CAPropertyAnimation

CAPropertyAnimation也是个抽象类,本身不具备动画效果,只有子类才有,主要存在以下方法和属性

@interface CAPropertyAnimation : CAAnimation

+ (instancetype)animationWithKeyPath:(nullable NSString *)path;

@property(nullable, copy) NSString *keyPath;
@property(getter=isAdditive) BOOL additive;
@property(getter=isCumulative) BOOL cumulative;
@property(nullable, strong) CAValueFunction *valueFunction;

@end

6.0 CABasicAnimation基础动画

CABasicAnimation 基础动画其实就是一段时间内发生的改变,最简单的形式就是从一个值改变到另一个值,

@property(nullable, strong) id fromValue;
@property(nullable, strong) id toValue;
@property(nullable, strong) id byValue;

下面这段英文摘自苹果官方文档,将的是fromValue toValue ByValue 怎么使用
The interpolation values are used as follows:

7. CAKeyframeAnimation关键帧动画

CAKeyframeAnimation 关键帧动画,该动画从字面简单的理解为做动画时只需要给出动画部分关键帧即可,系统会通过插值方式自己完成动画,在文件中存在以属性:

/** General keyframe animation class. **/
@interface CAKeyframeAnimation : CAPropertyAnimation

@property(nullable, copy) NSArray *values;
@property(nullable) CGPathRef path;
@property(nullable, copy) NSArray<NSNumber *> *keyTimes;
@property(nullable, copy) NSArray<CAMediaTimingFunction *> *timingFunctions;
@property(copy) NSString *calculationMode;
@property(nullable, copy) NSArray<NSNumber *> *tensionValues;
@property(nullable, copy) NSArray<NSNumber *> *continuityValues;
@property(nullable, copy) NSArray<NSNumber *> *biasValues;
@property(nullable, copy) NSString *rotationMode;

@end

7.1 values属性

values属性为NSArray类型,里面的元素称为“关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
代码如下:

   CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position.y";
    animation.values = @[@150,@400,@100,@400];
    animation.duration = 5.f;
  
    [self.testLayer addAnimation:animation forKey:nil];

执行动画效果:

关键帧动画.gif

7.2 keyTimes属性

keyTimes可以为对应的关键帧指定对应的时间点,其取值范围为0~1.0,keyTimes中的每一个时间值都对应values中的每一帧。如果没有设置keyTimes,各个关键帧的时间是平分的

keyTimes值要和运行时间段吻合,如果不吻合会出现以下问题:
第一种情况:吻合情况

    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position.y";
    animation.values = @[@400,@0,@400,@0];//定义了4帧
    animation.duration = 5.f;
    
    //设置每一个关键帧的时间
    animation.keyTimes = @[@0,@(1/8.),@(3/8.),@1];
    
    [self.testLayer addAnimation:animation forKey:nil];

效果如下:

关键帧动画添加keyTimes属性.gif
分析:
添加keyTimes记录着执行到每一帧动画的时间点,例如:
@400 @0 为起始值
@400 - > @0过程中,必须在@(1/8.)执行到@0位置
@0 - >@400过程中,必须在@(3/8.)执行到@ 400位置
@400 - > @0过程中,必须在@1执行到@0位置
第二种情况:少给值了,少给则提前结束
 //设置每一个关键帧的时间
    animation.keyTimes = @[@0,@(1/8.),@1];

运行效果:

keyTime值数量过少.gif

分析:
添加keyTimes记录着执行到每一帧动画的时间点,例如:
@400 @0 为起始值
@400 - > @0过程中,必须在@(1/8.)执行到@0位置
@0 - >@400过程中,必须在@(1)执行到@ 400位置,由于@1时间结束,所以动画直接结束

第三种情况:多给值了,则按照顺序执行,剩余时间无效

CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position.y";
    animation.values = @[@400,@0,@400,@0];//定义了4帧
    animation.duration = 5.f;
    
    //设置每一个关键帧的时间
    animation.keyTimes = @[@0,@(1/8.),@(2/8.),@(3/8.),@(4/8.),@1];
    
    [self.testLayer addAnimation:animation forKey:nil];

效果:


keyTimes多给情况.gif

分析:
添加keyTimes记录着执行到每一帧动画的时间点,例如:
@400 @0 为起始值
@400 - > @0过程中,必须在@(1/8.)执行到@0位置
@0 - >@400过程中,必须在@(2/8.)执行到@ 400位置
@400 - > @0过程中,必须在@@(3/8.)执行到@0位置
剩余时间@(3/8.) -> @1这个时间段属于无效冗余时间,所以动画在等待到@1这一时刻立即结束。

7.3. timingFunctions

该属性在上述已经介绍过了,只不过是在动画每一个执行路径过程中执行的时间类型函数,如果你values个数为N,则timingFunctions 个数至少为N-1;否则剩余部分则执行线性时间函数。

代码例子:

    animation.timingFunctions = @[
                                         [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                                         [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
                                         [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut],
                                         [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];

运行效果:

timingFunctions时间函数.gif

7.4. calculationMode属性

在关键帧动画中还有一个非常重要的参数,那便是calculationMode,计算模式.其主要针对的是每一帧的内容为一个座标点的情况,也就是对anchorPoint 和 position 进行的动画.当在平面座标系中有多个离散的点的时候,可以是离散的,也可以直线相连后进行插值计算,也可以使用圆滑的曲线将他们相连后进行插值计算. calculationMode目前提供如下几种模式 kCAAnimationLinear

对于这五种效果分别如下

1.gif

8. CAAnimationGroup组动画

组动画,比较简单为两个动画的结合体

@interface CAAnimationGroup : CAAnimation

@property(nullable, copy) NSArray<CAAnimation *> *animations;

@end

代码如下:

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic,weak) CALayer *myLayer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
 
    CALayer *myLayer=[[CALayer alloc] init];
    self.myLayer=myLayer;
 
    myLayer.bounds=CGRectMake(0, 0, 100, 30);

    myLayer.position=CGPointMake(200, 200);

    myLayer.backgroundColor=[UIColor redColor].CGColor;
    
    [self.view.layer addSublayer:myLayer];

}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    

    //第一组动画
    CAKeyframeAnimation  *keyframe=[CAKeyframeAnimation animationWithKeyPath:nil];
 
    keyframe.keyPath=@"position";
    
    UIBezierPath *path=[UIBezierPath bezierPathWithArcCenter:CGPointMake(200 , 200) radius:150 startAngle:0 endAngle:2*M_PI  clockwise:YES];
    keyframe.path=path.CGPath;
    
    //第二组
    CABasicAnimation *basic=[CABasicAnimation animationWithKeyPath:nil];

    basic.keyPath=@"transform.rotation";
    basic.byValue=@(2*M_PI *20);
    
    //把两组动画组成组动画
    CAAnimationGroup *group=[[CAAnimationGroup alloc] init];

    group.duration=10;
    group.repeatCount=INT16_MAX;
    group.animations=@[keyframe,basic];
    
    [self.myLayer addAnimation:group forKey:nil];
    
    
}

@end

运行效果:

组动画.gif

9. CATransition转场动画


/** Transition animation subclass. **/

@interface CATransition : CAAnimation

@property(copy) NSString *type;
@property(nullable, copy) NSString *subtype;
@property float startProgress;
@property float endProgress;
@property(nullable, strong) id filter;

@end

9.1 type动画过渡类型

主要提供了4中过渡方式


私有API

下面看下suckEffect效果:

suckEffect.gif

9.2 subtype动画过渡方向

规定了动画从哪个方面过来

9.2 startProgress和endProgress动画起点/终点百分比

 transition.startProgress = 0.5;

效果:

startProgress = 0.5.gif

由此可以看出:动画初始时刻已经执行了0.5,所以我们看到的是执行剩余部分的0.5部分。

transition.endProgress = 0.8;

效果:

endProgress = 0.8.gif

由此可以看出:动画在执行到0.8进度的时候,突然结束

10. CASpringAnimation弹簧动画


@interface CASpringAnimation : CABasicAnimation

@property CGFloat mass;//目标质量
@property CGFloat stiffness;//劲度系数
@property CGFloat damping;//阻尼系数
@property CGFloat initialVelocity;//初始速度
@property(readonly) CFTimeInterval settlingDuration;//结算时间,结算时间 返回弹簧动画到停止时的估算时间,根据当前的动画参数估算
通常弹簧动画的时间使用结算时间比较准确

@end

代码示例:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic,strong) CALayer *testLayer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    CALayer *testLayer = [CALayer new];
    self.testLayer = testLayer;
    
    [self.view.layer addSublayer:testLayer];
    
    testLayer.backgroundColor = [UIColor redColor].CGColor;
    testLayer.frame = CGRectMake(150, 100, 100, 100);
    
    UIView *lineView1 = [[UIView alloc ] initWithFrame:CGRectMake(0, self.testLayer.position.y+200.0, [UIScreen mainScreen].bounds.size.width, 1)];
    [self.view addSubview:lineView1];
    lineView1.backgroundColor = [UIColor blueColor];
    
    UIView *lineView2 = [[UIView alloc ] initWithFrame:CGRectMake(0, self.testLayer.position.y, [UIScreen mainScreen].bounds.size.width, 1)];
    [self.view addSubview:lineView2];
    lineView2.backgroundColor = [UIColor blackColor];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
   
    CASpringAnimation *spring  =[CASpringAnimation animation];
    spring.keyPath = @"position.y";
    spring.fromValue = @(self.testLayer.position.y);
    spring.toValue = @(self.testLayer.position.y+200.0);
    
    spring.mass = 5;//质量
    spring.damping = 30;//阻尼系数
    spring.stiffness = 300;//劲度系数
    spring.initialVelocity = 0;//初始化速度
    spring.duration = spring.settlingDuration;//动画执行时间
   
    NSLog(@"settlingDuration=%lf",spring.settlingDuration);
    [self.testLayer addAnimation:spring forKey:nil];
}

@end

运行效果:

CASpringAnimation弹簧动画.gif
上一篇 下一篇

猜你喜欢

热点阅读