美文网首页
第八篇:layer的显式动画

第八篇:layer的显式动画

作者: 意一ineyee | 来源:发表于2018-01-19 16:49 被阅读34次

在上一篇中我们研究了layer的隐式动画,隐式动画是一个很棒的实现layer层基础动画的方式,但是它并不能实现layer层的关键帧动画和过渡动画,所以本篇将学习layer的显式动画,显式动画可分为:属性动画(基础动画和关键帧动画)、动画组过渡动画

第二篇隐式动画我们说到隐式动画可以直接作用于layer的可动画属性,但是不能直接作用于UIView关联的那个layer的可动画属性,因为UIKit框架把UIView关联的layer的隐式动画给禁用了。而显式动画就不一样了,显式动画也是作用于layer层的,但是它既可以作用于直接创建的layer,也可以作用于view关联的layer,所以我们就不必区分了,直接怼layer就可以了。


目录

一、属性动画(CAPropertyAnimation)

1、基础动画(CABasicAnimation)
(1)什么是基础动画?
(2)如何使用基础动画?
(3)举例说明基础动画需要注意的三个地方及解决
2、关键帧动画(CAKeyframeAnimation)
(1)什么是关键帧动画?
(2)如何使用关键帧动画?
(3)举例说明设置关键帧的两个方法:数组关键帧法和路径关键帧法

二、动画组(CAAnimationGroup)

(1)什么是动画组?
(2)如何使用动画组?

三、过渡动画

(1)什么是过渡动画?
(2)如何使用过渡动画?

四、什么情况下使用显示动画


一、属性动画(CAPropertyAnimation)

我们知道想要做属性动画的话,可以直接使用UIView的block属性动画,也可以使用layer的隐式动画来实现,那么这里的layer显示动画的属性动画就是我们实现属性动画的第三种选择。

属性动画又有两个子类,我们平常主要使用这两个子类来做动画,它们是基础动画(CABasicAnimation)关键帧动画(CAKeyframeAnimation),接下来我们将分别介绍:

1、基础动画(CABasicAnimation)
(1)什么是基础动画?

第一篇中UIView block基础动画说过了,类似。

(2)如何使用基础动画?

基础动画的使用也很简单,只需要按以下7个步骤来就可以了:

  • 创建一个基础动画
  • 设置好基础动画的代理(回调中要做事的)
  • 指定基础动画要改变的属性
  • 给基础动画一个初始值(不给也行,会默认为layer初始状态对应属性的值)
  • 给基础动画一个结束值
  • 再指定一些基础动画的其它设置(如动画时长、重复次数等)
  • 然后把这个基础动画添加到layer上,动画就会开始执行了
(3)举个例子

给customLayer背景色的改变加一个动画,给customLayer1切圆角的改变加一个动画

(清单3.1)

//
//  ViewController.m
//  CoreAnimation
//
//  Created by 意一yiyi on 2017/11/13.
//  Copyright © 2017年 意一yiyi. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<CAAnimationDelegate>

@property (strong, nonatomic) CALayer *customLayer;
@property (strong, nonatomic) CALayer *customLayer1;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.customLayer = [[CALayer alloc] init];
    self.customLayer.frame = CGRectMake(100, 100, 100, 100);
    self.customLayer.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:self.customLayer];
    
    self.customLayer1 = [[CALayer alloc] init];
    self.customLayer1.frame = CGRectMake(100, 250, 100, 100);
    self.customLayer1.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:self.customLayer1];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    CABasicAnimation *basicAnimation = [[CABasicAnimation alloc] init];
    NSLog(@"customLayer背景色basicAnimation===%@", basicAnimation);
    basicAnimation.delegate = self;
    basicAnimation.keyPath = @"backgroundColor";
    basicAnimation.toValue = (__bridge id _Nullable)([UIColor orangeColor].CGColor));
    basicAnimation.duration = 3;
    [self.customLayer addAnimation:basicAnimation forKey:nil];
    
    CABasicAnimation *basicAnimation1 = [[CABasicAnimation alloc] init];
    NSLog(@"customLayer1切圆角basicAnimation1===%@", basicAnimation1);
    basicAnimation1.delegate = self;
    basicAnimation1.keyPath = @"cornerRadius";
    basicAnimation1.toValue = @(50);
    basicAnimation1.duration = 3;
    [self.customLayer1 addAnimation:basicAnimation1 forKey:nil];
}

- (void)animationDidStart:(CAAnimation *)anim {
    
    NSLog(@"动画开始了===%@", anim);
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    
    NSLog(@"动画结束了===%@", anim);
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
1.gif

运行,我们可以看到确实有了动画,但是也发现了如下问题:

  • 动画在完成之后又跳回到初始状态了,这是为什么?
    • 针对这个问题:其实基础动画只是虚拟出来的视觉效果,并没有真正改变layer做动画那个属性的值,所以我们要在动画结束把layer做动画的那个属性的值修改为动画的结束值。同时我们要注意在动画结束重新设置layer属性值的时候,要记得关闭layer的隐式动画,否则既会有基础动画带来的动画也会有隐式动画带来的动画,就重叠了。修改动画结束的代理方法如下:

(清单3.2)

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    
    NSLog(@"动画结束了===%@", anim);
    
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.customLayer.backgroundColor = [UIColor orangeColor].CGColor;
    self.customLayer1.cornerRadius = 50;
    [CATransaction commit];
}
1.gif

运行,发现可以了,但是又发现两个问题:

  • 改变layer属性值的时候,layer迅速地闪回到初始状态才变为结束值,这是为什么?(模拟器是没有问题的,所以调试动画一定要在真机上整)

    • 针对这个问题:我们需要在动画结束之后,设置layer保持哪一帧,需要设置为保持动画结束后的那一帧,并且在此之前设置动画结束后不自动移除,而是在需要的时候手动移除。通过这两个属性的设置(即animationremovedOnCompletionfillMode两个属性),动画在结束时就不会快速闪回初始状态再变为结束状态了,代码如下:

(清单3.3)

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    CABasicAnimation *basicAnimation = [[CABasicAnimation alloc] init];
    NSLog(@"customLayer背景色basicAnimation===%@", basicAnimation);
    basicAnimation.delegate = self;
    basicAnimation.keyPath = @"backgroundColor";
    basicAnimation.toValue = (__bridge id _Nullable)([UIColor orangeColor].CGColor);
    basicAnimation.duration = 3;
    basicAnimation.removedOnCompletion = NO;// 动画结束后不自动移除动画
    basicAnimation.fillMode = kCAFillModeForwards;// 动画结束后保持结束时的那一帧
    [self.customLayer addAnimation:basicAnimation forKey:nil];
    
    CABasicAnimation *basicAnimation1 = [[CABasicAnimation alloc] init];
    NSLog(@"customLayer1切圆角basicAnimation1===%@", basicAnimation1);
    basicAnimation1.delegate = self;
    basicAnimation1.keyPath = @"cornerRadius";
    basicAnimation1.toValue = @(50);
    basicAnimation1.duration = 3;
    basicAnimation1.removedOnCompletion = NO;
    basicAnimation1.fillMode = kCAFillModeForwards;
    [self.customLayer1 addAnimation:basicAnimation1 forKey:nil];
}
  • 还有一个问题就是,如果我们在一个控制器里面创建了多个动画的话(比如(清单3.1)中我们分别给customLayer和customLayer1创建了一个动画),那么这多个动画都会走到同一个动画结束的代理方法里,我们想要达到的动画目的是customLayer的动画结束时改变customLayer的背景色,customLayer1的动画结束时改变customLayer1的圆角,而不是(清单3.1)中那种把customLayer和customLayer1两者同时改变,那怎么做呢?有三种方法,我们分别看一下:

    • 方法一:我们看到动画结束的代理方法是有个参数anim把动画带回来的,那是否我们可以把我们创建的动画创建成属性,来这边比对,对上号之后改变相应layer的属性不就可以了嘛。看似很简单完美的方法。但是你也看到我们有打印操作了,看下控制台的输出吧:

(清单3.3)

2018-01-16 11:06:09.205151+0800 CoreAnimation[1582:390479] customLayer背景色basicAnimation===<CABasicAnimation: 0x1c803aa40>
2018-01-16 11:06:09.205444+0800 CoreAnimation[1582:390479] customLayer1切圆角basicAnimation1===<CABasicAnimation: 0x1c803ab20>
2018-01-16 11:06:09.205989+0800 CoreAnimation[1582:390479] 动画开始了===<CABasicAnimation: 0x1c803ab40>
2018-01-16 11:06:09.206101+0800 CoreAnimation[1582:390479] 动画开始了===<CABasicAnimation: 0x1c803ab00>
2018-01-16 11:06:12.207372+0800 CoreAnimation[1582:390479] 动画结束了===<CABasicAnimation: 0x1c803ab40>
2018-01-16 11:06:12.207838+0800 CoreAnimation[1582:390479] 动画结束了===<CABasicAnimation: 0x1c803ab00>

我们惊奇地发现,我们创建的基础动画和动画开始、结束代理方法带回来的动画并不是一个动画。因为动画开始、结束代理方法带回来的动画是我们创建的基础动画的深拷贝,omg,这种方法失败了。

  • 方法二:回到(清单3.1)可以看到,我们在给layer添加动画的方法-addAnimation: forKey:的key我们给的是空,其实我们可以在这个地方给animation关联一个key,这个key其实就是我们能获取到指定animation的唯一标识。我们修改代码如下:

(清单3.4)

//
//  ViewController.m
//  CoreAnimation
//
//  Created by 意一yiyi on 2017/11/13.
//  Copyright © 2017年 意一yiyi. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<CAAnimationDelegate>

@property (strong, nonatomic) CALayer *customLayer;
@property (strong, nonatomic) CALayer *customLayer1;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.customLayer = [[CALayer alloc] init];
    self.customLayer.frame = CGRectMake(100, 100, 100, 100);
    self.customLayer.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:self.customLayer];
    
    self.customLayer1 = [[CALayer alloc] init];
    self.customLayer1.frame = CGRectMake(100, 250, 100, 100);
    self.customLayer1.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:self.customLayer1];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    CABasicAnimation *basicAnimation = [[CABasicAnimation alloc] init];
    NSLog(@"customLayer背景色basicAnimation===%@", basicAnimation);
    basicAnimation.delegate = self;
    basicAnimation.keyPath = @"backgroundColor";
    basicAnimation.toValue = (__bridge id _Nullable)([UIColor orangeColor].CGColor);
    basicAnimation.duration = 3;
    [self.customLayer addAnimation:basicAnimation forKey:@"basicAnimation"];
    
    CABasicAnimation *basicAnimation1 = [[CABasicAnimation alloc] init];
    NSLog(@"customLayer1切圆角basicAnimation1===%@", basicAnimation1);
    basicAnimation1.delegate = self;
    basicAnimation1.keyPath = @"cornerRadius";
    basicAnimation1.toValue = @(50);
    basicAnimation1.duration = 3;
    [self.customLayer1 addAnimation:basicAnimation1 forKey:@"basicAnimation1"];
}

- (void)animationDidStart:(CAAnimation *)anim {
    
    NSLog(@"动画开始了===%@", anim);
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.customLayer.backgroundColor = [UIColor orangeColor].CGColor;
    self.customLayer1.cornerRadius = 50;
    [CATransaction commit];
    
    NSLog(@"动画结束了===%@, %@", [self.customLayer animationForKey:@"basicAnimation"], self.customLayer1.animationKeys);
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

但是运行,发现输出都是空,不知道为什么?待解。

(清单3.5)

2018-01-16 13:12:46.985628+0800 CoreAnimation[1695:434274] customLayer背景色basicAnimation===<CABasicAnimation: 0x1c803e280>
2018-01-16 13:12:46.986083+0800 CoreAnimation[1695:434274] customLayer1切圆角basicAnimation1===<CABasicAnimation: 0x1c803e360>
2018-01-16 13:12:46.986961+0800 CoreAnimation[1695:434274] 动画开始了===<CABasicAnimation: 0x1c803e380>
2018-01-16 13:12:46.987066+0800 CoreAnimation[1695:434274] 动画开始了===<CABasicAnimation: 0x1c803e340>
2018-01-16 13:12:49.988287+0800 CoreAnimation[1695:434274] 动画结束了===(null), (null)
2018-01-16 13:12:49.988567+0800 CoreAnimation[1695:434274] 动画结束了===(null), (null)
  • 方法三:幸运的是,我们还有第三种办法。就是Core Animation实现了KVC,所以可以随便给一个动画贴标签,而且key和value都是id类型的,所以此处我们就可以吧layer作为value给基础动画贴个标签,然后再动画结束的回调里就可以判断出具体是哪个动画结束了。修改代码如下:

(清单3.6)

//
//  ViewController.m
//  CoreAnimation
//
//  Created by 意一yiyi on 2017/11/13.
//  Copyright © 2017年 意一yiyi. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<CAAnimationDelegate>

@property (strong, nonatomic) CALayer *customLayer;
@property (strong, nonatomic) CALayer *customLayer1;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.customLayer = [[CALayer alloc] init];
    self.customLayer.frame = CGRectMake(100, 100, 100, 100);
    self.customLayer.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:self.customLayer];
    
    self.customLayer1 = [[CALayer alloc] init];
    self.customLayer1.frame = CGRectMake(100, 250, 100, 100);
    self.customLayer1.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:self.customLayer1];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    CABasicAnimation *basicAnimation = [[CABasicAnimation alloc] init];
    NSLog(@"customLayer背景色basicAnimation===%@", basicAnimation);
    basicAnimation.delegate = self;
    basicAnimation.keyPath = @"backgroundColor";
    basicAnimation.toValue = (__bridge id _Nullable)([UIColor orangeColor].CGColor);
    basicAnimation.duration = 3;
    [basicAnimation setValue:self.customLayer forKey:@"tag"];
    [self.customLayer addAnimation:basicAnimation forKey:nil];
    
    CABasicAnimation *basicAnimation1 = [[CABasicAnimation alloc] init];
    NSLog(@"customLayer1切圆角basicAnimation1===%@", basicAnimation1);
    basicAnimation1.delegate = self;
    basicAnimation1.keyPath = @"cornerRadius";
    basicAnimation1.toValue = @(50);
    basicAnimation1.duration = 3;
    [basicAnimation1 setValue:self.customLayer1 forKey:@"tag"];
    [self.customLayer1 addAnimation:basicAnimation1 forKey:nil];
}

- (void)animationDidStart:(CAAnimation *)anim {
    
    NSLog(@"动画开始了===%@", anim);
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    
    if ([anim valueForKey:@"tag"] == self.customLayer) {
        
        [CATransaction begin];
        [CATransaction setDisableActions:YES];
        self.customLayer.backgroundColor = [UIColor orangeColor].CGColor;
        [CATransaction commit];
    }
    
    if ([anim valueForKey:@"tag"] == self.customLayer1) {
        
        [CATransaction begin];
        [CATransaction setDisableActions:YES];
        self.customLayer1.cornerRadius = 50;
        [CATransaction commit];
    }
    
    NSLog(@"动画结束了===%@, %@", [self.customLayer animationForKey:@"basicAnimation"], self.customLayer1.animationKeys);
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

2、关键帧动画(CAKeyframeAnimation)
(1)什么是关键帧动画?

关键帧动画同样也是属性动画的一个子类,它也是用来做layer层的属性动画的,它可以用来做一些比较复杂的属性动画。比如说,我们看到基础动画的一个弊端就是它只需要给一个初始值和结束值就可以了,这样的效果在情境下就显得生硬甚至不能满足需求。而关键帧动画就可以帮助我们摆脱基础动画仅仅是初始值和结束值的束缚,可以随意设置一连串关键帧,甚至是绘制曲线。

(2)如何使用关键帧动画?

关键帧动画的使用也很简单,只需要按以下7个步骤来就可以了:

  • 创建一个关键帧动画
  • 设置好关键帧动画的代理(回调中要做事的)
  • 指定关键帧动画要改变的属性
  • 给关键帧动画的values属性设置一写关键帧(注意第一帧不能省略,它不会默认为layer初始状态的属性值,第一帧我们要主动设置为layer初始状态的属性值,否则会从初始状态的值直接跳变到第一帧)的数组或者路径绘制设置关键帧法
  • 再指定一些基础动画的其它设置(如动画时长、重复次数等)
  • 然后把这个基础动画添加到layer上,动画就会开始执行了
(3)举个例子
  • values数组关键帧法:改变customLayer的背景色
    (清单3.7)
//
//  ViewController.m
//  CoreAnimation
//
//  Created by 意一yiyi on 2017/11/13.
//  Copyright © 2017年 意一yiyi. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<CAAnimationDelegate>

@property (strong, nonatomic) CALayer *customLayer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.customLayer = [[CALayer alloc] init];
    self.customLayer.frame = CGRectMake(100, 100, 100, 100);
    self.customLayer.backgroundColor = [UIColor redColor].CGColor;
    [self.view.layer addSublayer:self.customLayer];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    CAKeyframeAnimation *basicAnimation = [[CAKeyframeAnimation alloc] init];
    basicAnimation.delegate = self;
    basicAnimation.keyPath = @"backgroundColor";
    basicAnimation.values = @[
                              (__bridge id _Nullable)([UIColor redColor].CGColor),// 第一帧我们要主动设置为layer初始状态的属性值
                              (__bridge id _Nullable)([UIColor orangeColor].CGColor),
                              (__bridge id _Nullable)([UIColor yellowColor].CGColor),
                              (__bridge id _Nullable)([UIColor greenColor].CGColor)
                              ];
    basicAnimation.duration = 3;
    [basicAnimation setValue:self.customLayer forKey:@"tag"];
    [self.customLayer addAnimation:basicAnimation forKey:nil];
}

- (void)animationDidStart:(CAAnimation *)anim {
    
    NSLog(@"动画开始了");
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.customLayer.backgroundColor = [UIColor greenColor].CGColor;
    [CATransaction commit];
    
    NSLog(@"动画结束了");
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
1.gif
  • 路径关键帧法:让layer沿一个三次贝塞尔曲线移动

我们知道UIView block关键帧动画只能设置数组关键帧,所以如果有的场景下,我们需要连续的关键帧就需要使用显式动画的关键帧动画了,它支持路径关键帧。

(清单3.8)

//
//  ViewController.m
//  CoreAnimation
//
//  Created by 意一yiyi on 2017/11/13.
//  Copyright © 2017年 意一yiyi. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<CAAnimationDelegate>

@property (strong, nonatomic) UIImageView *customImageView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.customImageView = [[UIImageView alloc] init];
    self.customImageView.frame = CGRectMake(100, 100, 100, 100);
    self.customImageView.backgroundColor = [UIColor clearColor];
    self.customImageView.image = [UIImage imageNamed:@"汽车"];
    [self.view addSubview:self.customImageView];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    CAKeyframeAnimation *keyframeAnimation = [[CAKeyframeAnimation alloc] init];
    keyframeAnimation.delegate = self;
    keyframeAnimation.keyPath = @"position";
    
    // 绘制一个三次贝塞尔曲线
    UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
    [bezierPath moveToPoint:CGPointMake(150, 150)];
    [bezierPath addCurveToPoint:CGPointMake(150, 450) controlPoint1:CGPointMake(250, 250) controlPoint2:CGPointMake(50, 350)];
    // 绘制一个shapeLayer放在屏幕上显得更形象
    CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
    shapeLayer.path = bezierPath.CGPath;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.strokeColor = [UIColor magentaColor].CGColor;
    shapeLayer.lineWidth = 3.0f;
    [self.view.layer addSublayer:shapeLayer];
    
    keyframeAnimation.path = bezierPath.CGPath;
    keyframeAnimation.duration = 3;
    keyframeAnimation.rotationMode = kCAAnimationRotateAuto;// 让layer根据曲线的切线自动旋转方向
    [keyframeAnimation setValue:self.customImageView forKey:@"tag"];
    [self.customImageView.layer addAnimation:keyframeAnimation forKey:nil];
}

- (void)animationDidStart:(CAAnimation *)anim {
    
    NSLog(@"动画开始了");
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.customImageView.layer.position = CGPointMake(150, 450);
    [CATransaction commit];
    
    NSLog(@"动画结束了");
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
1.gif

二、动画组

(1)什么是动画组?

动画组其实很简单了,它是另外一个继承于CAAnimation的子类,它提供了一个animations的数组属性用来把很多个熟悉感动画组合起来一起执行和控制,避免一堆动画单个控制的繁琐

当然了,我们不用组合动画而是把单个单个的动画分别添在同一个layer上也是可以的。

(2)如何使用动画组?

很简单了,创造单个单个的动画,然后把这些单个单个的动画赋值给动画组的animations数组,然后把这个动画组添加到layer上,这一堆动画就会开始同时执行了。

举例:把上面的基础动画和关键帧动画组合起来,构成动画组。
(清单3.9)

//
//  ViewController.m
//  CoreAnimation
//
//  Created by 意一yiyi on 2017/11/13.
//  Copyright © 2017年 意一yiyi. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<CAAnimationDelegate>

@property (strong, nonatomic) CALayer *customLayer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.customLayer = [[CALayer alloc] init];
    self.customLayer.frame = CGRectMake(100, 100, 100, 100);
    self.customLayer.backgroundColor = [UIColor redColor].CGColor;
    self.customLayer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"汽车"].CGImage);
    self.customLayer.contentsScale = [UIScreen mainScreen].scale;
    [self.view.layer addSublayer:self.customLayer];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    // 基础动画
    CABasicAnimation *basicAnimation = [[CABasicAnimation alloc] init];
    basicAnimation.keyPath = @"backgroundColor";
    basicAnimation.toValue = (__bridge id _Nullable)([UIColor orangeColor].CGColor);
    
    // 关键帧动画
    CAKeyframeAnimation *keyframeAnimation = [[CAKeyframeAnimation alloc] init];
    keyframeAnimation.keyPath = @"position";
    // 绘制一个三次贝塞尔曲线
    UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
    [bezierPath moveToPoint:CGPointMake(150, 150)];
    [bezierPath addCurveToPoint:CGPointMake(150, 450) controlPoint1:CGPointMake(250, 250) controlPoint2:CGPointMake(50, 350)];
    // 绘制一个shapeLayer放在屏幕上显得更形象
    CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
    shapeLayer.path = bezierPath.CGPath;
    shapeLayer.fillColor = [UIColor clearColor].CGColor;
    shapeLayer.strokeColor = [UIColor magentaColor].CGColor;
    shapeLayer.lineWidth = 3.0f;
    [self.view.layer addSublayer:shapeLayer];
    keyframeAnimation.path = bezierPath.CGPath;
    keyframeAnimation.rotationMode = kCAAnimationRotateAuto;// 让layer根据曲线的切线自动旋转方向
    
    // 动画组
    CAAnimationGroup *animationGroup = [[CAAnimationGroup alloc] init];
    animationGroup.delegate = self;
    animationGroup.animations = @[basicAnimation, keyframeAnimation];
    animationGroup.duration = 3;
    [self.customLayer addAnimation:animationGroup forKey:nil];
}

- (void)animationDidStart:(CAAnimation *)anim {
    
    NSLog(@"动画开始了");
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.customLayer.backgroundColor = [UIColor orangeColor].CGColor;
    self.customLayer.position = CGPointMake(150, 450);
    [CATransaction commit];
    
    NSLog(@"动画结束了");
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
1.gif

三、过渡动画

(1)什么是过渡动画?

第一篇说过了,类似。

过渡动画(CATransition)同样是CAAnimation的一个子类,但它不像属性动画那样是平滑地在两个属性值之间做动画,而是直接对整个图层做变化(即过渡动画不是针对图层的属性的,而是针对图层的,当然它可以用来做属性动画而不局限于做属性动画,我们甚至可以在不改变图层或者不知道改变了图层什么东西的情况下直接给图层添加一个过渡动画,也就是说过渡动画是给图层添的动画,只要有layer存在,我们任何时机任何情况都能给layer添加过渡动画,可以给layer添,也可以给view关联的layer添),即首先展示修改前的图层外观,然后通过一个过渡动画的效果变换到新的外观展示

(2)如何使用过渡动画?
  • 创建过渡动画
  • 设置好过渡动画的代理
  • 设置过渡动画类型, 过渡动画子类型, 过渡动画动画速度及其它一些设置
  • 设置图层的新外观
  • 把过渡动画添加到layer上,过渡动画就开始执行了

过渡动画类型(type):用来控制过渡动画的效果,有下面九种:

fade// 淡入淡出的效果(没有方向)
push// 新图层从一侧推入、旧图层从一侧推出来展示新外观效果
moveIn// 新图层从一侧推入来展示新外观效果、但旧图层没有从一侧推出
reveal// 旧图层从一侧推出来展示新外观效果、但新图层没有从一侧推入
oglFlip// 正面和背面的二维翻转效果
cube// 立方体翻转效果
pageCurl// 翻页效果
suckEffect// 收缩效果
rippleEffect// 水滴波纹效果(没有方向)

过渡动画子类型如下(subType):用来控制过渡动画的方向,有下面四种,type里除了fade和rippleEffect没有方向之外,其它效果都有方向

kCATransitionFromTop// 从底部开始过渡动画
kCATransitionFromLeft// 从左侧开始过渡动画
kCATransitionFromBottom// 从顶部开始过渡动画
kCATransitionFromRight// 从右侧开始过渡动画

转场动画缓冲曲线控制(timingFunction):有下面四种

kCAMediaTimingFunctionEaseInEaseOut// 动画慢进,逐渐加快,逐渐减慢,慢出
kCAMediaTimingFunctionEaseIn// 动画慢进,逐渐加快
kCAMediaTimingFunctionEaseOut// 动画逐渐减慢,慢出
kCAMediaTimingFunctionLinear// 动画匀速

可见layer显式动画的过渡动画效果要比UIView block过渡动画的效果多了好多,因此效果也会更丰富一些。

下面举个例子,类似第一篇的,切换imageView的图片和label的文本,只不过是个水纹效果:

//
//  ViewController.m
//  CoreAnimation
//
//  Created by 意一yiyi on 2017/11/13.
//  Copyright © 2017年 意一yiyi. All rights reserved.
//

#import "ViewController.h"

#define kScreenWidth [UIScreen mainScreen].bounds.size.width
#define kScreenHeight [UIScreen mainScreen].bounds.size.height

@interface ViewController ()<CAAnimationDelegate>

@property (strong, nonatomic) UIImageView *customImageView;
@property (strong, nonatomic) UILabel *label;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.customImageView = [[UIImageView alloc] init];
    self.customImageView.frame = CGRectMake(0, 64, kScreenWidth, kScreenWidth);
    self.customImageView.backgroundColor = [UIColor redColor];
    self.customImageView.image = [UIImage imageNamed:@"0.jpg"];
    [self.view addSubview:self.customImageView];

    self.label = [[UILabel alloc] init];
    self.label.frame = CGRectMake(0, kScreenWidth + 100, kScreenWidth, 30);
    self.label.text = @"奥黛丽·赫本";
    self.label.textColor = [UIColor blackColor];
    self.label.textAlignment = NSTextAlignmentCenter;
    [self.view addSubview:self.label];
}

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

    // 创建过渡动画
    CATransition *transition = [[CATransition alloc] init];

    // 设置好过渡动画的代理
    transition.delegate = self;

    // 设置过渡动画类型、子类型、时间曲线及其它一些设置
    transition.type = @"rippleEffect";
    transition.subtype = kCATransitionFromTop;
    transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.duration = 3;

    // 设置图层的新外观
    NSInteger num = arc4random()%7;
    self.customImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"%ld.jpg", num]];
    switch (num) {
        case 0:
            self.label.text = @"奥黛丽·赫本";
            break;
        case 1:
            self.label.text = @"苏菲·玛索";
            break;
        case 2:
            self.label.text = @"泰勒·斯威夫特";
            break;
        case 3:
            self.label.text = @"安妮·海瑟薇";
            break;
        case 4:
            self.label.text = @"娜塔丽·波特曼";
            break;
        case 5:
            self.label.text = @"凯拉·奈特利";
            break;
        case 6:
            self.label.text = @"杰西卡·阿尔芭";
            break;
    }

    // 把过渡动画添加到layer上
    [self.customImageView.layer addAnimation:transition forKey:nil];
    [self.label.layer addAnimation:transition forKey:nil];
}

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

    NSLog(@"动画开始了");
}

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

    NSLog(@"动画结束了");
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
1.gif

四、什么情况下使用显示动画

针对基础动画:用UIView block基础动画和layer的隐式动画就够了,layer的显式动画的基础动画也没多啥,用起来反而累赘。

针对关键帧动画:一般情况下使用UIView block关键帧动画就可以,但是如果是需要连续关键帧的场景就需要使用layer的显式动画的关键帧动画。

针对过渡动画:一般情况下使用UIView block过渡动画就可以,但是如果是需要更丰富动画效果的场景可以使用layer的显式动画的过渡动画。

总的来说,我们暂时还没有必要直接创建layer来做动画,开发中正常用UIView就可以了,UIView block动画就不说了,隐式动画和显示动画也都是可以间接或直接地作用于view关联的layer的。

相关文章

  • 第八篇:layer的显式动画

    在上一篇中我们研究了layer的隐式动画,隐式动画是一个很棒的实现layer层基础动画的方式,但是它并不能实现la...

  • 第六篇:UIView block动画

    在开始学习Core Animation提供的layer的隐式动画和layer的显式动画之前,我们先来总结一下UIV...

  • 显式动画Animation<转>

    当需要对非Root Layer进行动画或者需要对动画做更多自定义的行为的时候,就必须使用到显式动画了,显式动画的基...

  • iOS-CATransaction

    CATransaction 事务类,可以对多个layer的属性同时进行修改.它分隐式事务,和显式事务.区分隐式动画...

  • iOS CATransaction学习

    CATransaction 事务类,可以对多个layer的属性同时进行修改.它分隐式事务,和显式事务.区分隐式动画...

  • SwiftUI -- View 动画

    SwiftUI 中的动画有两种类型:显式动画和隐式动画。 一、显式动画 显式动画通过 withAnimation ...

  • CoreAnimation 理解

    隐式动画的理解: 1,layer层的动画都伴随着一个隐式动画(默认0.25秒的动画,例子:通过layer 层更改一...

  • iOS中显式和隐式动画的区别

    收集到的显式和隐式动画的区别: 1、隐式动画一直存在 如需关闭需设置;显式动画是不存在,如需显式 要开启(创建)。...

  • iOS面试题-每日十道-第四天

    一. 简述iOS动画机制 iOS分为显式动画,隐式动画 显式动画: 对一些属性做指定的自定义动画,或者创建非线性动...

  • iOS隐式动画与显式动画的区别

    请参考iOS隐式动画与显式动画的区别

网友评论

      本文标题:第八篇:layer的显式动画

      本文链接:https://www.haomeiwen.com/subject/pinjoxtx.html