iOS——Core Animation(核心动画)

1、核心动画的基本概念

Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍。也就是说,使用少量的代码就可以实现非常强大的功能。

Core Animation是跨平台的,可以用在Mac OS X和iOS平台。

Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。不阻塞主线程,可以理解为在执行动画的时候还能点击(按钮)。

CAAnimation 简介:

CAAimation是所有动画对象的基类,负责控制动画的持续时间和速度,是个抽象类,不能直接使⽤用,应该使⽤用它具体的⼦子类
属性说明:

  1. removedOnCompletion // 动画是否⾃自动移出

  2. duration // 动画持续时间

  3. speed //速度

  4. timeOffset // 动画时间的偏移

  5. repeatCount // 动画重复执⾏行的次数(HUGE_VALF无限次)

  6. repeatDuration // 动画重复执⾏行的总时间

  7. autoreverses // 反转动画

  8. delegate // 代理

  9. fillMode // 填充模式

  10. timingFunction //速度控制函数

  基本属性说明:

属性说明

duration

动画的持续时间

repeatCount

重复次数,无限循环可以设置HUGE_VALF或者MAXFLOAT

repeatDuration

重复时间

removedOnCompletion

默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillModekCAFillModeForwards

fillMode

决定当前对象在非active时间段的行为。比如动画开始之前或者动画结束之

beginTime

可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间

timingFunction

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

delegate

动画代理

fillMode属性的设置:

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

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

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

速度控制函数(CAMediaTimingFunction):

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

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

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

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

2、基本动画(CABasicAnimation)

CABasicAnimation 用于实现layer属性值从一个值(fromValue)到另外一个值(toValue)变化的简单动画,比如旋转、缩放、逐渐透明、移动等。

相关属性:

  • fromValue:keyPath相应属性的初始值

  • byValue:keyPath相应属性的中间值( 变化的值)

  • toValue:keyPath相应属性的结束值

动画过程说明:

  • 随着动画的进⾏行,在⻓长度为duration的持续时间内,keyPath相应属性的值从

fromValue渐渐地变为toValue。

  • keyPath内容是CALayer的可动画Animatable属性

  • 如果fillMode=kCAFillModeForwards同时removedOnComletion=NO,那么

在动画执行完毕后,图层会保持显示动画执行后的状态。但实质上,图层属性值还是动画执行前的初始值,并没有真正被改变。

代码事例如下:

#import "ViewController.h"

@interface ViewController (){
    
    CALayer *aniLayer;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    aniLayer = [CALayer layer];
    aniLayer.frame = CGRectMake(100, 50, 100, 100);
    aniLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"足球"].CGImage);
    
    [self.view.layer addSublayer:aniLayer];
    
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    // 给layer 添加动画
    [aniLayer addAnimation:[self positionAnimation] forKey:@"position"];
    [aniLayer addAnimation:[self rotationAnimation] forKey:@"rotation"];
    
//    [aniLayer addAnimation:[self boundsAnimation] forKey:@"bounds"];
    
//    [aniLayer addAnimation:[self scaleAnimation] forKey:@"scale"];
    
}


- (CAAnimation *)positionAnimation {
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 50)];
    animation.toValue = [NSValue valueWithCGPoint:CGPointMake(150, 550)];
    animation.duration = 2.0;
    
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    animation.autoreverses = YES;
    animation.repeatCount = HUGE_VALF; //  HUGE_VALF 最大浮点数,表示无限次重复
    
    /* 动画的线性变换(动画速度变化)
     kCAMediaTimingFunctionLinear 匀速
     kCAMediaTimingFunctionEaseIn 加速
     kCAMediaTimingFunctionEaseOut 减速
     kCAMediaTimingFunctionEaseInEaseOut 缓慢进入缓慢出去
     kCAMediaTimingFunctionDefault 默认
     */
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    

    return animation;
}


- (CAAnimation *)boundsAnimation {
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"bounds"];
    animation.fromValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 100, 100)];
    animation.toValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 300, 300)];
    
    animation.duration = 2.0;
    
    return animation;
}

- (CAAnimation *)rotationAnimation {
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    // animation.fromValue = @0;
    // animation.toValue = @(2 * M_PI);
    animation.byValue = @( -2 * M_PI);
    
    animation.duration = 2.0;
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    animation.autoreverses = YES;
    animation.repeatCount = HUGE_VALF; //  HUGE_VALF 最大浮点数,表示无限次重复
    
    return animation;
}


- (CAAnimation *)scaleAnimation {
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    
    // 1、初始值
    animation.fromValue = @1.0;
    // 2、目标值
    animation.toValue = @2.0;
    
    // 3、变化的值, fromValue ~ toValue 值的变化量
     // animation.byValue = @1.0;
    
    // 4、动画时间
    animation.duration = 2.0;
    
    /* 5、动画的填充模式:
     kCAFillModeForwards
     kCAFillModeBackwards
     kCAFillModeBoth
     kCAFillModeRemoved
    */
    animation.fillMode = kCAFillModeForwards;
    
    // 6、动画后是否移除动画后的状态(回到原始状态),默认是YES, 前提是要设置fillModle为:kCAFillModeForwards
    animation.removedOnCompletion = NO;
    
    // 7、是否有回复效果
    animation.autoreverses = YES;
    
    // 8、设置动画重复次数
    animation.repeatCount = HUGE_VALF; //  HUGE_VALF 最大浮点数,表示无限次重复
    
    // 9、播放动画的速度
    animation.speed = 2;
    
    return animation;
}

@end

暂停动画:

-(void)stopAnimation{
     // CACurrentMediaTime(): 当前媒体时间,表示系统启动后到当前的秒数,当系统重启后这个时间也重置
    CFTimeInterval stopTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
       // 设置动画的时间的偏移
    layer.timeOffset = stopTime;
    
    layer.speed = 0;
}

继续动画:

-(void)resumeAnimation{
    // 获取暂停时的时间
    CFTimeInterval stopTime = [layer timeOffset];
    
    layer.speed = 1;
    layer.timeOffset = 0;
    layer.beginTime = 0;
    
    // 设置开始的时间(继续动画,这样设置相当于让动画等待的秒数等于暂停的秒)
    layer.beginTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - stopTime;
}

代码说明:

  • 设置的animationWithKeyPath是@"position",说明要修改的是CALayer的position属性,也就是会执行平移动画

  • animation.fromValue,animation.toValue这里的属性接收的时id类型的参数,因此并不能直接使用CGPoint这种结构体类型,而是要先包装成NSValue对象后再使用。

  • 默认情况下,动画执行完毕后,动画会自动从CALayer上移除,CALayer又会回到原来的状态。为了保持动画执行后的状态,可以加入animation.removedOnCompletion = NO

3、关键帧动画(CAKeyframeAnimation)

CAKeyframeAnimation 可以给一个图层提供多个目标值(values)或者一个指定路径(path)的动画。关键帧动画有如下几个重要参数:

values指定图层属性(position、scale、rotation...)的多个目标值,这个图层就会由这些指定的值进行动画。

path一个CGPathRef类型的路径,指定图层就会沿着这个路径进行动画。

keyTimes关键帧指定对应的时间点,其取值范围为0到1.0。也就是keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间平分。

代码事例如下:


#import "ViewController.h"

#define TScreenWidth [UIScreen mainScreen].bounds.size.width
#define TScreenHeight [UIScreen mainScreen].bounds.size.height

@interface ViewController (){
    
    CALayer *aniLayer;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CALayer *bgLayer = [CALayer layer];
    bgLayer.frame = self.view.bounds;
    bgLayer.backgroundColor = [UIColor blackColor].CGColor;
    [self.view.layer addSublayer:bgLayer];
    bgLayer.delegate = self;
    
    // 重绘
    [bgLayer setNeedsDisplay];
    
    
    aniLayer = [CALayer layer];
    aniLayer.bounds = CGRectMake(0 , 0, 100, 100);
    aniLayer.position = CGPointMake(TScreenWidth / 2, 50);
    aniLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"足球"].CGImage);
    [self.view.layer addSublayer:aniLayer];
    
    
    
   
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    
    [aniLayer addAnimation:[self keyframeAnimation] forKey:@"keyAnimation"];
}


- (CAAnimation *)keyframeAnimation {
    
    CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    keyAnimation.duration = 4;
//    keyAnimation.autoreverses = YES;
    keyAnimation.repeatCount = HUGE_VALF;
    keyAnimation.fillMode = kCAFillModeForwards;
    keyAnimation.removedOnCompletion = NO;
    
    
    /*
    NSValue *point_1 = [NSValue valueWithCGPoint:CGPointMake(TScreenWidth / 2,0)];
    NSValue *point_2 = [NSValue valueWithCGPoint:CGPointMake(50,TScreenHeight / 2)];
    NSValue *point_3 = [NSValue valueWithCGPoint:CGPointMake(TScreenWidth / 2,TScreenHeight - 50)];
    NSValue *point_4 = [NSValue valueWithCGPoint:CGPointMake(TScreenWidth - 50,TScreenHeight / 2)];
    NSValue *point_5 = [NSValue valueWithCGPoint:CGPointMake(TScreenWidth / 2,0)];
    
    // values:设置关键帧(多个目标点)
     keyAnimation.values = @[point_1,point_2,point_3,point_4,point_5];
    
    // 设置每一帧所在的时间比例
     keyAnimation.keyTimes = @[@0, @0.2, @0.5, @0.6,@1.0];
    
     */
    /* 插值计算模式:
        kCAAnimationLinear  关键帧之间进行插值计算(线性的)
        kCAAnimationDiscrete 关键帧之间不进行插值计算(离散的)
        kCAAnimationPaced 关键帧之间匀速切换,keyTimes\timingFunctions的设置将不起作用
        kCAAnimationCubic 关键帧进行圆滑曲线相连后插值计算
        kCAAnimationCubicPaced 匀速并且关键帧进行圆滑曲线相连后插值计算
     */
     keyAnimation.calculationMode = kCAAnimationLinear;
    
    
    keyAnimation.path = [self path].CGPath;
    
    return keyAnimation;
    
}


// 绘制路径
- (UIBezierPath *)path {
    
    // 椭圆
//    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:self.view.bounds];
    // 圆角矩形
//    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.view.bounds cornerRadius:50];
    // 内切圆
//    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(10, 100, 300, 300)];
    
    // 贝塞尔曲线
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(0, TScreenHeight)];
    CGPoint point_1 = CGPointMake(TScreenWidth, TScreenHeight);
    CGPoint controPoint_1 = CGPointMake(TScreenWidth / 2, - TScreenHeight);
//    CGPoint controPoint_2 = CGPointMake(TScreenWidth / 4 * 3, TScreenHeight);
    
    [path addQuadCurveToPoint:point_1 controlPoint:controPoint_1];
//    [path addCurveToPoint:point_1 controlPoint1:controPoint_1 controlPoint2:controPoint_2];
    
    return path;
}


// 绘图
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
    
    CGContextAddPath(ctx , [self path].CGPath);
    CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
    CGContextSetLineWidth(ctx, 5);
    CGContextDrawPath(ctx, kCGPathStroke);
}

@end

 

  • CAKeyframeAnimation——计算模式属性

  •  在关键帧动画中还有一个非常重要的参数,那便是calculationMode,所谓计算模式:其主要针对的是每一帧的内容为一个座标点的情况,也就是对anchorPoint和 position进行的动画

  • 当在平面座标系中有多个离散的点的时候,可以是离散的,也可以直线相连后进行插值计算,也可以使用圆滑的曲线将他们相连后进行插值计算

  • calculationMode目前提供如下几种模式:

       kCAAnimationLinear: 默认值,表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算 

       kCAAnimationDiscrete: 离散的,不进行插值计算,所有关键帧直接逐个进行显示

       kCAAnimationPaced :使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions无效

       kCAAnimationCubic: 对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,这里的主要目的是使得运行的轨迹变得圆滑

       kCAAnimationCubicPaced :看这个名字就知道和kCAAnimationCubic有一定联系,其实就是在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的

4、转场动画(CATransition)

  • CATransition是CAAnimation的子类,用于做转场动画

  • 能够为图层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点

     如:UINavigationController导航控制器就是通过CATransition转场动画实现了将控制器的视图推入屏   幕的动画效果

转场动画就是从一个场景以动画的形式过渡到另一个场景。转场动画的使用一般分为以下几个步骤:
 
 1.创建转场动画 CATransition
 
 2.设置转场类型transtion.type、子类型transtion.subtype(可选)及其他属性
 
 3.设置转场后的新视图并添加动画到图层
 
 下表列出了常用的转场类型(注意私有API是苹果官方没有公开的动画类型,但是目前通过仍然可以使用):

  •  公开的API

    fade 淡出效果  kCATransitionFade
    movein  新视图移动到旧视图上   kCATransitionMoveIn
    push 新视图退出旧视图上  kCATransitionPush
    reveal 移开旧视图显示新视图   kCATransitionReveal

  •     私有的API

    cube 立体翻转效果
    oglFlip  翻转效果
    suckEffect 收缩效果
    rippleEffect 水滴波纹效果
    pageCurl 向上翻页效果
    pageUnCurl 向下翻页效果
    cameralIrisHollowOpen 摄像头打开效果
    cameraIrisHollowClose  摄像头关闭效果

 

代码事例如下:


#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    static int index = 1;
    
    [self.imageView.layer addAnimation:[self transitionAnimation] forKey:nil];
    
    NSString *imageName = [NSString stringWithFormat:@"large_%d.jpg",index];
    self.imageView.image = [UIImage imageNamed:imageName];
    
    index++;
    
    if (index > 10) {
        
        index = 0;
    }
    
}


- (CAAnimation *)transitionAnimation {
    
    CATransition *transitionAni = [CATransition animation];
    transitionAni.duration = 1.0;
    /*
     1. fade     淡出效果
     2. moveIn 进入效果
     3. push    推出效果
     4. reveal   移出效果
     
     // 未公开的
     5. cube   立方体翻转效果
     6. suckEffect  抽走效果
     7. rippleEffect 水波效果
     8. pageCurl    翻开页效果
     9. pageUnCurl 关闭页效果
     10. cameraIrisHollowOpen  相机镜头打开效果
     11.  cameraIrisHollowClose  相机镜头关闭效果
    */
    
    transitionAni.type = kCATransitionPush;
    // transitionAni.type = @"push";
    
    // 转场的方向:`fromLeft', `fromRight', `fromTop'  `fromBottom'
    transitionAni.subtype = @"fromTop";
    
    // 开始转场和结束转场的进度位置
    // transitionAni.startProgress = 0.5;
    // transitionAni.endProgress = 1;
    
    return transitionAni;
}

@end

5、动画组(CAAnimationGrup)

CATransition是CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点。

UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果。

  •  相关属性:  1.type:动画过渡类型

                  2.subtype:动画过渡⽅方向

                  3.startProgress:动画起点

                  4.endProgress:动画终点

  •  type属性说明:

                   1.kCATransitionFade:淡⼊入淡出

                   2.kCATransitionMoveIn:新视图移到旧视图上⾯面

                   3.kCATransitionPush:新视图把旧视图推出

                   4.kCATransitionReveal:把旧视图移开,显⽰示下⾯面的新视图


代码事例如下:

#import "ViewController.h"

#define TScreenWidth [UIScreen mainScreen].bounds.size.width

#define TScreenHeight [UIScreen mainScreen].bounds.size.height

@interface ViewController ()


@property (weak, nonatomic) IBOutlet UIView *bgView;
@property (weak, nonatomic) IBOutlet UIImageView *aniImageView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

- (void)viewDidAppear:(BOOL)animated {
    
    [super viewDidAppear:animated];
    
    self.bgView.layer.delegate = self;
    [self.bgView.layer setNeedsDisplay];
    
    NSLog(@"%@",NSStringFromCGRect(self.aniImageView.frame));
    NSLog(@"%f, %f",TScreenWidth, TScreenHeight);
    
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    
    [self.aniImageView.layer addAnimation:[self animationGroup] forKey:@"group"];
}

- (CAAnimation *)positionAnimation {
    
    CAKeyframeAnimation *keyAni = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    
    keyAni.duration = 3.0;
    keyAni.fillMode = kCAFillModeForwards;
    keyAni.removedOnCompletion = NO;
    keyAni.path = [self path].CGPath;
    
    return keyAni;
}


- (CAAnimation *)rotationAnimation {
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];

    animation.beginTime = 3.0;
    animation.duration = 2.0;
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    animation.byValue = @(2 * M_PI);
    
    return animation;
}

- (CAAnimation *)downAnimation {
    
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    animation.beginTime = 3.0;
    animation.duration = 2.0;
    animation.byValue = [NSValue valueWithCGPoint:CGPointMake(0, TScreenHeight)];
    
    return animation;
}


- (UIBezierPath *)path  {
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(0, TScreenHeight)];
    CGPoint toPoint = CGPointMake(TScreenWidth, 0);
    
    CGPoint controlPoint = CGPointMake(TScreenWidth,TScreenHeight);
    [path addQuadCurveToPoint:toPoint controlPoint:controlPoint];
    
    return path;
}


- (CAAnimation *)animationGroup {

    // 创建一个动画组,用于组合多种动画
    CAAnimationGroup * aniGroup = [CAAnimationGroup animation];
    
    // 动画组的完成时间
    aniGroup.duration = 5.0;
    aniGroup.fillMode = kCAFillModeForwards;
    aniGroup.removedOnCompletion = NO;
    // 组合多个动画
    aniGroup.animations = @[[self positionAnimation],[self rotationAnimation],[self downAnimation]];
    return aniGroup;
}


- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {

    CGContextAddPath(ctx, [self path].CGPath);
    CGContextSetLineWidth(ctx, 5);
    CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
    CGContextDrawPath(ctx, kCGPathStroke);
}

@end

 

转载于:https://my.oschina.net/nieqing/blog/738697

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值