iOS Core Animation 详解

3,078 阅读10分钟

一、概述

Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍。也就是说,使用少量的代码就可以实现非常强大的功能。它可以用在Mac OS X和iOS平台。Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。要注意的是,Core Animation是直接作用在CALayer上的,并非UIView

 

二、Core Animation的继承结构

CAAnimation是所有动画对象的父类,负责控制动画的持续时间和速度、是个抽象类,不能直接使用,应该使用具体子类。需要注意的是CAAnimation 和 CAPropertyAnimation 都是抽象类。

view是负责响应事件的,layer是负责显示的。

2248583-2dfdb6de25cde9d1

黄色的区块是常用的属性、方法或者需要遵守的协议,灰色的是名称。

图注:

  • 核心动画中所有类都遵守CAMediaTiming。
  • CAAnaimation是个抽象类,不具备动画效果,必须用它的子类(CAAnimationGroup和CATransition)才有动画效果。
  • CAAnimationGroup(动画组),可以同时进行缩放,旋转。
  • CATransition(转场动画),界面之间跳转都可以用转场动画。
  • CAPropertyAnimation也是个抽象类,本身不具备动画效果,只有子类(CABasicAnimation和CAKeyframeAnimation)才有动画效果。
  • CABasicAnimation(基础动画),做一些简单效果。
  • CAKeyframeAnimation(帧动画),做一些连续的流畅的动画。

 

三、Core Animation使用步骤及相关属性

1、使用步骤

第一步:初始化一个CAAnimation对象,并设置一些动画相关属性。

第二步:通过调用CALayer的addAnimation:forKey:方法增加CAAnimation对象到CALayer中,这样就能开始执行动画了。

第三步:通过调用CALayer的removeAnimationForKey:方法可以停止CALayer中的动画。

 

2、常用属性

就是上面图片中的小黄色区内容。

duration:持续时间,默认值是0.25秒

repeatCount:重复次数,无线循环可以设置HUGE_VALF或者CGFLOAT_MAX

repeatDuration:重复时间

removeOnCompletion: 默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到执行动画之前的状态。如果想要图层保持显示动画执行后的状态,那就设置为NO,同时设置fillMode为kCAFillModeForwards

fillMode:决定当前对象在非active时间段的行为

beginTime:可以用来设置动画延时执行,若想延迟2s,就设置为CACurrentMediaTIme() + 2

CACurrentMediaTIme():图层的当前时间

timingFunction:速度控制函数,控制动画运行节奏

delegate:动画代理

动画代理能够检测动画的执行和结束:

@interface NSObject (CAAnimationDelegate)
 - (void)animationDidStart:(CAAnimation *)anim;
 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
@end

 

3、animationWithKeyPath中,常用的keyPath

Snip20170211_6_2

 

4、动画填充模式

kCAFillModeForwards:当动画结束后,layer会一直保持着动画最后的状态

kCAFillModeBackwards:在动画开始前,只需要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始

kCAFillModeBoth:这个其实就是上面两个合成,动画加入后,开始之前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态

kCAFillModeRemoved:这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态

5、动画速度控制函数

kCAMediaTimingFunctionLinear(线性):匀速,给你一个相对静态的感觉

kCAMediaTimingFunctionEaseIn(渐进):动画缓慢进入,然后加速离开

kCAMediaTimingFunctionEaseOut(渐出):动画全速进入,然后减速的到达目的地

kCAMediaTimingFunctionEaseInEaseOut(渐进渐出):动画缓慢的进入,中间加速,然后减速的到达目的地。这个是默认的动画行为。

6、CALayer上动画的暂停和恢复

#pragma mark 暂停CALayer的动画
-(void)pauseLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];

    // 让CALayer的时间停止走动
      layer.speed = 0.0;
    // 让CALayer的时间停留在pausedTime这个时刻
    layer.timeOffset = pausedTime;
}
#pragma mark 恢复CALayer的动画
-(void)resumeLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = layer.timeOffset;
    // 1. 让CALayer的时间继续行走
      layer.speed = 1.0;
    // 2. 取消上次记录的停留时刻
      layer.timeOffset = 0.0;
    // 3. 取消上次设置的时间
      layer.beginTime = 0.0;
    // 4. 计算暂停的时间(这里也可以用CACurrentMediaTime()-pausedTime)
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    // 5. 设置相对于父坐标系的开始时间(往后退timeSincePause)
      layer.beginTime = timeSincePause;
}

四、动画的调用方式

(1)UIView 代码块调用:

_demoView.frame = CGRectMake(0, SCREEN_HEIGHT/2-50, 50, 50);

[UIView animateWithDuration:1.0f animations:^{
    _demoView.frame = CGRectMake(SCREEN_WIDTH, SCREEN_HEIGHT/2-50, 50, 50);
} completion:^(BOOL finished) {
    _demoView.frame = CGRectMake(SCREEN_WIDTH/2-25, SCREEN_HEIGHT/2-50, 50, 50);
}];

(2)UIView [begin commit]模式

_demoView.frame = CGRectMake(0, SCREEN_HEIGHT/2-50, 50, 50);
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0f];
_demoView.frame = CGRectMake(SCREEN_WIDTH, SCREEN_HEIGHT/2-50, 50, 50);
[UIView commitAnimations];

(3)使用Core Animation中的类

CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"position"];
anima.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, SCREEN_HEIGHT/2-75)];
anima.toValue = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH, SCREEN_HEIGHT/2-75)];
anima.duration = 1.0f;
[_demoView.layer addAnimation:anima forKey:@"positionAnimation"];

addanimation:forkey: 这个key到底是什么?官方的注释是:

/** Animation methods. **/

/* Attach an animation object to the layer. Typically this is implicitly
 * invoked through an action that is an CAAnimation object.
 *
 * 'key' may be any string such that only one animation per unique key
 * is added per layer. The special key 'transition' is automatically
 * used for transition animations. The nil pointer is also a valid key.
 *
 * If the `duration' property of the animation is zero or negative it
 * is given the default duration, either the value of the
 * `animationDuration' transaction property or .25 seconds otherwise.
 *
 * The animation is copied before being added to the layer, so any
 * subsequent modifications to `anim' will have no affect unless it is
 * added to another layer. */

翻译下:

/ * *动画方法。* * // 
* 将动画对象附加到层。通常这是隐式地
* 通过一个动作是一个CAAnimation对象调用。
* “key”可以是任何字符串,这样每个唯一键只有一个动画
* 添加每层。特殊键自动“过渡”
* 用于过渡动画。空指针也是一个有效的关键。
* 如果动画的“持续时间”属性是零或消极
* 默认时间,给出的值
*  animationDuration事务属性或。25秒。
*  动画复制之前添加到层,所以任何后续修改“动画”没有影响,除非它是添加到另一层

也就是说,key可以设置为任意值。

 

五、基本使用示例

1、基础动画(CABaseAnimation)

重要属性:

fromValue : keyPath对应的初始值

toValue : keyPath对应的结束值

基础动画主要提供了对于CALayer对象中的可变属性进行简单动画的操作。比如:位移、透明度、缩放、旋转、背景色等等。

例如:

//
//  ViewController.m
//  AnimationTest
//
//  Created by 李峰峰 on 2017/2/11.
//  Copyright © 2017年 李峰峰. All rights reserved.
//

#import "ViewController.h"

#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height

@interface ViewController ()

@property(nonatomic,strong)UIView *myView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CGFloat myViewW = 100;
    CGFloat myViewH = 100;
    CGFloat myViewX = (SCREEN_WIDTH - myViewW)/2;
    CGFloat myViewY = (SCREEN_HEIGHT - myViewH)/2;
    self.myView = [[UIView alloc]initWithFrame:CGRectMake(myViewX, myViewY, myViewW, myViewH)];
    [self.view addSubview:self.myView];
    self.myView.backgroundColor = [UIColor redColor];

}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //使用CABasicAnimation创建基础动画
    CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"position"];
    anima.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, SCREEN_HEIGHT/2-75)];
    anima.toValue = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH, SCREEN_HEIGHT/2-75)];
    anima.duration = 1.0f;
    //anima.fillMode = kCAFillModeForwards;
    //anima.removedOnCompletion = NO;
    [self.myView.layer addAnimation:anima forKey:@"positionAnimation"];
    
}


@end

运行结果:

2017-02-11 22.08.34

其他一些动画效果:

/**
 *  透明度动画
 */
-(void)opacityAniamtion{
    CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"opacity"];
    anima.fromValue = [NSNumber numberWithFloat:1.0f];
    anima.toValue = [NSNumber numberWithFloat:0.2f];
    anima.duration = 1.0f;
    [_demoView.layer addAnimation:anima forKey:@"opacityAniamtion"];
}

/**
 *  缩放动画
 */
-(void)scaleAnimation{ 
    CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"transform.scale"];//同上
    anima.toValue = [NSNumber numberWithFloat:2.0f];
    anima.duration = 1.0f;
    [_demoView.layer addAnimation:anima forKey:@"scaleAnimation"]; 
}

/**
 *  旋转动画
 */
-(void)rotateAnimation{
    CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];//绕着z轴为矢量,进行旋转(@"transform.rotation.z"==@@"transform.rotation")
    anima.toValue = [NSNumber numberWithFloat:3*M_PI];
    anima.duration = 1.0f;
    [_demoView.layer addAnimation:anima forKey:@"rotateAnimation"];
}

/**
 *  背景色变化动画
 */
-(void)backgroundAnimation{
    CABasicAnimation *anima = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
    anima.toValue =(id) [UIColor greenColor].CGColor;
    anima.duration = 1.0f;
    [_demoView.layer addAnimation:anima forKey:@"backgroundAnimation"];
}

2、CAKeyframeAnimation 关键帧动画

Keyframe顾名思义就是关键点的frame,你可以通过设定CALayer的始点、中间关键点、终点的frame,时间,动画会沿你设定的轨迹进行移动 。

CAKeyframeAnimation 的一些比较重要的属性:

path:这是一个 CGPathRef 对象,默认是空的,当我们创建好CAKeyframeAnimation的实例的时候,可以通过制定一个自己定义的path来让某一个物体按照这个路径进行动画。这个值默认是nil,当其被设定的时候,values 这个属性就被覆盖。

values:一个数组,提供了一组关键帧的值,当使用path的 时候 values的值自动被忽略。
1 2 3 path:这是一个 CGPathRef 对象,默认是空的,当我们创建好CAKeyframeAnimation的实例的时候,可以通过制定一个自己定义的 path来让某一个物体按照这个路径进行动画。这个值默认是nil,当其被设定的时候,values 这个属性就被覆盖。   values:一个数组,提供了一组关键帧的值,当使用path的 时候 values的值自动被忽略。

例如,连续移动一个view到不同的位置:

/**
 *  关键帧动画 values
 */
-(void)keyFrameAnimation{
    CAKeyframeAnimation *anima = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    NSValue *value0 = [NSValue valueWithCGPoint:CGPointMake(0, SCREEN_HEIGHT/2-50)];
    NSValue *value1 = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH/3, SCREEN_HEIGHT/2-50)];
    NSValue *value2 = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH/3, SCREEN_HEIGHT/2+50)];
    NSValue *value3 = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH*2/3, SCREEN_HEIGHT/2+50)];
    NSValue *value4 = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH*2/3, SCREEN_HEIGHT/2-50)];
    NSValue *value5 = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH, SCREEN_HEIGHT/2-50)];
    anima.values = [NSArray arrayWithObjects:value0,value1,value2,value3,value4,value5, nil];
    anima.duration = 2.0f;
    anima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];//设置动画的节奏
    anima.delegate = self;//设置代理,可以检测动画的开始和结束
    [_demoView.layer addAnimation:anima forKey:@"keyFrameAnimation"];
}

/**
 *  path动画
 */
-(void)pathAnimation{
    CAKeyframeAnimation *anima = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(SCREEN_WIDTH/2-100, SCREEN_HEIGHT/2-100, 200, 200)];
    anima.path = path.CGPath;
    anima.duration = 2.0f;
    [_demoView.layer addAnimation:anima forKey:@"pathAnimation"];
}

运行效果就不贴出来了,大家可以自己尝试下。

对比基础动画跟关键帧动画,关键帧动画引入了动画占比时长的概念,这让我们能控制每个关键帧动画的占用比例而不是传入一个无意义的动画时长 —— 这让我们的代码更加难以理解。当然,除了动画占比之外,关键帧动画的options参数也让动画变得更加平滑,下面是关键帧特有的配置参数:

UIViewKeyframeAnimationOptionCalculationModeLinear // 连续运算模式,线性

UIViewKeyframeAnimationOptionCalculationModeDiscrete // 离散运算模式,只显示关键帧

UIViewKeyframeAnimationOptionCalculationModePaced // 均匀执行运算模式,线性

UIViewKeyframeAnimationOptionCalculationModeCubic // 平滑运算模式

UIViewKeyframeAnimationOptionCalculationModeCubicPaced // 平滑均匀运算模式

3、CAAnimationGroup 组合动画

Group也就是组合的意思,可以保存一组动画对象,将CAAnimationGroup对象加入图层后,组中所有动画对象可以同时并发运行。

其重要属性为:

animations:用来保存一组动画对象的NSArray

注意:默认情况下,一组动画对象是同时运行的,我们也可以通过设置动画对象的beginTime属性来更改动画的开始时间。

如下:

-(void)groupAnimation1{
 // 位移动画
  CAKeyframeAnimation *anima1 = [CAKeyframeAnimation animationWithKeyPath:@"position"];
  NSValue *value0 = [NSValue valueWithCGPoint:CGPointMake(0, SCREEN_HEIGHT/2-50)];
  NSValue *value1 = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH/3, SCREEN_HEIGHT/2-50)];
  NSValue *value2 = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH/3, SCREEN_HEIGHT/2+50)];
  NSValue *value3 = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH*2/3, SCREEN_HEIGHT/2+50)];
  NSValue *value4 = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH*2/3, SCREEN_HEIGHT/2-50)];
  NSValue *value5 = [NSValue valueWithCGPoint:CGPointMake(SCREEN_WIDTH, SCREEN_HEIGHT/2-50)];
  anima1.values = [NSArray arrayWithObjects:value0,value1,value2,value3,value4,value5, nil];

  //缩放动画
  CABasicAnimation *anima2 = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
  anima2.fromValue = [NSNumber numberWithFloat:0.8f];
  anima2.toValue = [NSNumber numberWithFloat:2.0f];

  //旋转动画
  CABasicAnimation *anima3 = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
  anima3.toValue = [NSNumber numberWithFloat:M_PI*4];

  //组动画
  CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
  groupAnimation.animations = [NSArray arrayWithObjects:anima1,anima2,anima3, nil];
  groupAnimation.duration = 4.0f;

  [_demoView.layer addAnimation:groupAnimation forKey:@"groupAnimation"];
  }

运行效果:

2017-02-11 22.32.29

4、CATransition 过渡动画

CAAnimation的子类,用于做过渡动画或者转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。

重要属性:

type:动画过渡类型

subtype:动画过渡方向

startProgress:动画起点(在整体动画的百分比)

endProgress:动画终点(在整体动画的百分比)

有以下几种type:
Snip20170211_8

有以下几种subtype:

kCATransitionFromRight 从右侧进入
kCATransitionFromLeft 从左侧进入
kCATransitionFromTop 从顶部进入
kCATransitionFromBottom 从底部进入

例如,为了便于使用,我们可以自定义一个相关方法:

#pragma CATransition动画实现
- (void) transitionWithType:(NSString *) type WithSubtype:(NSString *) subtype ForView : (UIView *) view
{
    //创建CATransition对象
    CATransition *animation = [CATransition animation];
    //设置运动时间
    animation.duration = DURATION;
    //设置运动type
    animation.type = type;
    if (subtype != nil) {
        //设置子类
        animation.subtype = subtype;
    }
    //设置运动速度
    animation.timingFunction = UIViewAnimationOptionCurveEaseInOut;
    [view.layer addAnimation:animation forKey:@"animation"];
}

除了上面layer的动画之外,还有UIView的动画:

Snip20170211_9

例如:

    [UIView beginAnimations:@"animationID" context:nil];
    [UIView setAnimationDuration:0.5f]; //动画时长
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.myView cache:YES]; //给视图添加过渡效果
    //在这里写你的代码.
    [UIView commitAnimations]; //提交动画

运行效果:

2017-02-11 22.57.43

以上就是Core Animation的主要用法。

PS:终于整理完了,累成狗了~

原创文章,转载请注明: 转载自李峰峰博客

本文链接地址: iOS Core Animation详解