iOS-CoreAnimation核心动画

2019-11-08  本文已影响0人  Imkata

学完上一篇iOS-CAlayer之后,我们就可以学习一下核心动画了。

一. CoreAnimation简介

1. 层级

首先看了解一下层级: 框架层级.png

其中上面的层级可以访问下面的层级,可以看出CoreAnimation也会使用CoreGraphics里面的一些东西。

2. CoreAnimation可以做什么?

进入CoreAnimation.h文件

#include <QuartzCore/CABase.h>
#include <QuartzCore/CATransform3D.h>

#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <QuartzCore/CAAnimation.h>
#import <QuartzCore/CADisplayLink.h>  //定时器相关
#import <QuartzCore/CAEAGLLayer.h>
#import <QuartzCore/CAEmitterCell.h>
#import <QuartzCore/CAEmitterLayer.h>  //一些layer层
#import <QuartzCore/CAGradientLayer.h>
#import <QuartzCore/CALayer.h>  //我们熟悉的layer层
#import <QuartzCore/CAMediaTiming.h>
#import <QuartzCore/CAMediaTimingFunction.h> //时间相关
#import <QuartzCore/CAReplicatorLayer.h>
#import <QuartzCore/CAScrollLayer.h>
#import <QuartzCore/CAShapeLayer.h>
#import <QuartzCore/CATextLayer.h>
#import <QuartzCore/CATiledLayer.h>
#import <QuartzCore/CATransaction.h>
#import <QuartzCore/CATransform3D.h>   //矩阵转换
#import <QuartzCore/CATransformLayer.h>
#import <QuartzCore/CAValueFunction.h>
#endif

可以发现CoreAnimation不单单包含动画还包含一些其他的东西,具体可看上面注释。

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

3. CAAnimation继承关系

CAAnimation继承图如下: CAAnimation.png
  1. CAPropertyAnimation是CAAnimation的子类,但是不能直接使用,要想创建动画对象,应该使用它的两个子类:CABasicAnimation和CAKeyframeAnimation。
  2. CAMediaTiming是一个协议(protocol)。
  3. 能用的动画类只有4个子类:CABasicAnimation、CAKeyframeAnimation、CAAnimationGroup、CATransition。

部分属性解析:

其中带*的表示来自CAMediaTiming协议的属性。

duration: *//动画的持续时间
repeatCount: *//动画的重复次数
repeatDuration: *//动画的重复时间
fillMode: *//决定当前对象在非active时间段的行为,比如动画开始之前,动画结束之后
beginTime: *//可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间
removedOnCompletion://默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards
timingFunction://速度控制函数,控制动画运行的节奏
delegate://动画代理

二. CABasicAnimation

CABasicAnimation是CAPropertyAnimation的子类。

属性解析:

fromValue: //keyPath相应属性的初始值
toValue: //keyPath相应属性的结束值

随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值从fromValue渐渐地变为toValue。

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

1. CABasicAnimation的简单使用

比如进行平移、旋转、缩放的动画,代码如下:

#import "ViewController.h"

@interface ViewController () <CAAnimationDelegate>

@property(nonatomic,strong) CALayer *myLayer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //创建layer
    CALayer *myLayer=[CALayer layer];
    //设置layer的属性
    myLayer.bounds=CGRectMake(0, 0, 150, 60);
    myLayer.backgroundColor=[UIColor yellowColor].CGColor;
    myLayer.position=CGPointMake(100, 100);
    myLayer.anchorPoint=CGPointMake(0, 0);
    myLayer.cornerRadius=40;
    //添加layer
    self.myLayer=myLayer;
    [self.view.layer addSublayer:myLayer];
}

//设置动画
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //[self pingyi];
    //[self suofang];
    [self xuanzhuan];
}

//平移
- (void)pingyi {
    //1.创建核心动画
    // CABasicAnimation *anima=[CABasicAnimation animationWithKeyPath:<#(NSString *)#>]
    CABasicAnimation *anima=[CABasicAnimation animation];
    
    //1.1告诉系统要执行什么样的动画
    anima.keyPath=@"position";
    //设置通过动画,将layer从哪儿移动到哪儿
    anima.fromValue=[NSValue valueWithCGPoint:CGPointMake(0, 0)];
    anima.toValue=[NSValue valueWithCGPoint:CGPointMake(200, 300)];
    
    //1.2设置动画执行完毕之后不删除动画
    anima.removedOnCompletion=NO;
    //1.3设置保存动画的最新状态
    anima.fillMode = @"forwards";
    //anim.fillMode = kCAFillModeForwards; // 和上面一行一样的
    
    anima.delegate = self;
    NSString *str = NSStringFromCGPoint(self.myLayer.position);
    NSLog(@"执行前:%@",str);
    
    //2.添加核心动画到layer
    [self.myLayer addAnimation:anima forKey:nil];
}

//缩放
- (void)suofang {
    //缩放动画可以实现心跳效果
    //创建动画对象
    CABasicAnimation *anim = [CABasicAnimation animation];
    
    //设置属性值
    anim.keyPath = @"transform.scale";
    anim.toValue = @0;
    
    //设置动画执行次数
    anim.repeatCount = MAXFLOAT;
    //设置动画执行时长
    anim.duration = 3;
    //自动反转(怎么样去 怎么样回来)
    anim.autoreverses = YES;
    
    //添加动画
    [self.myLayer addAnimation:anim forKey:nil];
}

//旋转
- (void)xuanzhuan {
    //1.创建动画
    CABasicAnimation *anima=[CABasicAnimation animationWithKeyPath:@"transform"];
    //1.1设置动画执行时间
    anima.duration=2.0;
    //1.2修改属性,执行动画
    anima.toValue=[NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI_2+M_PI_4, 1, 1, 0)];
    //1.3设置动画执行完毕后不删除动画
    anima.removedOnCompletion=NO;
    //1.4设置保存动画的最新状态
    anima.fillMode=kCAFillModeForwards;
    
    //2.添加动画到layer
    [self.myLayer addAnimation:anima forKey:nil];
}

#pragma mark - CAAnimationDelegate代理

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

-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    //动画执行完毕,打印执行完毕后的position值
    NSString *str = NSStringFromCGPoint(self.myLayer.position);
    NSLog(@"执行后:%@",str);
}
@end
平移: 平移.gif 缩放: 缩放.gif 旋转: 旋转.gif

打印结果:

2019-11-08 14:21:41.030264+0800 核心动画[10803:9330751] 执行前:{100, 100}
2019-11-08 14:21:41.031078+0800 核心动画[10803:9330751] 开始执行动画
2019-11-08 14:21:41.282279+0800 核心动画[10803:9330751] 执行后:{100, 100}

打印position的属性值,验证了,进行平移动画后,图层的属性值还是动画执行前的初始值{100,100},并没有真正被改变为{200,300}。

2. 贝塞尔曲线

UIBezierPath是苹果封装的路径,一般作为动画的路径(推荐使用)。

下面使用贝塞尔曲线和CABasicAnimation实现如下效果: 效果.gif

代码:

- (void)animationCaseOne {
    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bg-mine.png"]];
    imageView.frame = CGRectMake(100.f, 200.f, 100.f, 100.f);
    imageView.clipsToBounds = YES;
    [self.view addSubview:imageView];
    
    //添加一层浅白色layer
    layer = [CAShapeLayer layer];
    
    //CAShapeLayer占用空间比较少,CAShapeLayer和UIBezierPath是黄金搭档
    /*
     圆心
     半径
     开始角度
     结束角度
     顺时针
     */
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50.f, 50.f) radius:50.f startAngle:-M_PI_2 endAngle:3*M_PI/2 clockwise:YES];
    layer.path = path.CGPath;
    layer.fillColor = [UIColor clearColor].CGColor; //填充色,默认是黑色的
    layer.strokeColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5f].CGColor; //绘制色
    //线宽设置为半径100
    layer.lineWidth = 100.f;
    
    [imageView.layer addSublayer:layer];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //strokeStart单位属性0-1 开始画 结束画
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
    anim.duration = 4.f;
    anim.fromValue = @0;
    anim.toValue = @1;
    [layer addAnimation:anim forKey:nil];
}
下面有另外一个需求,如下实现如下图片: 效果图.png

有三种方式实现,代码如下,就不解释了

/*
 shapelayer.fillRule:1、kCAFillRuleEvenOdd 2、kCAFillRulenonzero
 
 nonzero字面意思是“非零”。按该规则,要判断一个点是否在图形内,从该点作任意方向的一条射线,然后检测射线与图形路径的交点情况。从0开始计数,路径从左向右穿过射线则计数加1,从右向左穿过射线则计数减1。得出计数结果后,如果结果是0,则认为点在图形外部,否则认为在内部
 evenodd字面意思是“奇偶”。按该规则,要判断一个点是否在图形内,从该点作任意方向的一条射线,然后检测射线与图形路径的交点的数量。如果结果是奇数则认为点在内部,是偶数则认为点在外部
 */
- (void)animationCaseTwoMethod1 {
    
    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bg-mine.png"]];
    imageView.frame = CGRectMake(100.f, 200.f, 100.f, 100.f);
    [self.view addSubview:imageView];
    
    //矩形
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0.f, 0.f, 100.f, 100.f)];
    [path addArcWithCenter:CGPointMake(50.f, 50.f) radius:50.f startAngle:0 endAngle:M_PI*2 clockwise:YES];
    
    layer = [CAShapeLayer layer];
    layer.path = path.CGPath;
    layer.fillRule = kCAFillRuleEvenOdd; //改变判断是不是在圆内的规则
    
    layer.fillColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5f].CGColor; //然后画上去
    [imageView.layer addSublayer:layer];
}

- (void)animationCaseTwoMethod2 {
    
    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bg-mine.png"]];
    imageView.frame = CGRectMake(100.f, 200.f, 100.f, 100.f);
    [self.view addSubview:imageView];
    
    //rct
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0.f, 0.f, 100.f, 100.f)];
    UIBezierPath *pathOne = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50.f, 50.f) radius:50.f startAngle:0 endAngle:M_PI*2 clockwise:YES];
    [path appendPath:[pathOne bezierPathByReversingPath]]; //两条线当成一条线
    
    layer = [CAShapeLayer layer];
    layer.path = path.CGPath;
    
    layer.fillColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5f].CGColor; //然后画上去
    [imageView.layer addSublayer:layer];
}

- (void)animationCaseTwoMethod3 {
    
    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"bg-mine.png"]];
    imageView.frame = CGRectMake(100.f, 200.f, 100.f, 100.f);
    [self.view addSubview:imageView];
    
    UIView *maskView = [[UIView alloc] initWithFrame:CGRectMake(0.f, 0.f, 100.f, 100.f)];
    maskView.backgroundColor = [UIColor colorWithWhite:1.f alpha:0.4f];
    
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0.f, 0.f, 100.f, 100.f)];
    UIBezierPath *pathOne = [UIBezierPath bezierPathWithArcCenter:CGPointMake(50.f, 50.f) radius:50.f startAngle:0 endAngle:M_PI*2 clockwise:YES];
    [path appendPath:[pathOne bezierPathByReversingPath]];
    
    layer = [CAShapeLayer layer];
    layer.path = path.CGPath;
    
    maskView.layer.mask = layer; //layer的遮罩就是我们画出的两条线
    
    [imageView addSubview:maskView];
}

三. CAKeyframeAnimation

  1. CAKeyframeAnimation也是CApropertyAnimation的子类。
  2. CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CAKeyframeAnimation会使用一个NSArray保存这些数值。

属性解析:

values: //就是上述的NSArray对象。里面的元素称为”关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
path: //可以设置一个CGPathRef\CGMutablePathRef,让层跟着路径移动。path只对CALayer的anchorPoint和position起作用。如果你设置了path,那么values将被忽略
keyTimes: //可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平分的

说明:CABasicAnimation可看做是最多只有2个关键帧的CAKeyframeAnimation。

代码:

#import "ViewController.h"

@interface ViewController () <CAAnimationDelegate>

@property (weak, nonatomic) UIView *customView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    view.backgroundColor = [UIColor orangeColor];
    self.customView = view;
    [self.view addSubview:view];
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //1.创建核心动画
    CAKeyframeAnimation *keyAnima=[CAKeyframeAnimation animation];
    //平移
    keyAnima.keyPath=@"position";
    //1.1告诉系统要执行什么动画
    NSValue *value1=[NSValue valueWithCGPoint:CGPointMake(100, 100)];
    NSValue *value2=[NSValue valueWithCGPoint:CGPointMake(200, 100)];
    NSValue *value3=[NSValue valueWithCGPoint:CGPointMake(200, 200)];
    NSValue *value4=[NSValue valueWithCGPoint:CGPointMake(100, 200)];
    NSValue *value5=[NSValue valueWithCGPoint:CGPointMake(100, 100)];
    keyAnima.values=@[value1,value2,value3,value4,value5];
     //1.2设置动画执行完毕后,不删除动画
    keyAnima.removedOnCompletion=NO;
    //1.3设置保存动画的最新状态
    keyAnima.fillMode=kCAFillModeForwards;
    //1.4设置动画执行的时间
    keyAnima.duration=4.0;
     //1.5设置动画的节奏
    keyAnima.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

    //设置代理,开始—结束
    keyAnima.delegate=self;
    //2.添加核心动画
    [self.customView.layer addAnimation:keyAnima forKey:nil];
}

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

-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    NSLog(@"结束动画");
}
@end
动画效果: 位移.gif

除了使用values也可以使用path,让动画跟着路径移动。上面的动画也可以使用path,代码如下:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //1.创建动画对象
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
    
    anim.duration = 2;
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(50, 50)];
    [path addLineToPoint:CGPointMake(300, 50)];
    [path addLineToPoint:CGPointMake(300, 400)];
    
    anim.keyPath =  @"position";
    anim.path = path.CGPath;
    
    //设置代理
    anim.delegate=self;

    self.customView.layer.anchorPoint = CGPointZero;
    [self.customView.layer addAnimation:anim forKey:nil];
}
动画效果: path.gif

也可以设置弧度实现抖动效果,如下:

#define angle2Radian(angle) ((angle) / 180.0 * M_PI)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //1.创建核心动画
    CAKeyframeAnimation *keyAnima=[CAKeyframeAnimation animation];
    keyAnima.keyPath=@"transform.rotation";
    //设置动画时间
    keyAnima.duration=0.1;
    //设置图标抖动弧度
    //把度数转换为弧度  度数/180*M_PI
    keyAnima.values=@[@(-angle2Radian(4)),@(angle2Radian(4)),@(-angle2Radian(4))];
    //设置动画的重复次数(设置为最大值)
    keyAnima.repeatCount=MAXFLOAT;
    keyAnima.fillMode=kCAFillModeForwards;
    keyAnima.removedOnCompletion=NO;
    //2.添加动画
    [self.customView.layer addAnimation:keyAnima forKey:nil];
}
效果图: 抖动.gif

最后不要忘记移除动画:

[self.customView.layer removeAnimationForKey:@"wendingding"];

示例:

下面再讲一个如何使用关键帧动画和贝塞尔曲线实现如下动画

关键帧动画.gif

代码:

#import "EOCKeyFrameAnimationViewController.h"

@interface EOCKeyFrameAnimationViewController ()<CAAnimationDelegate> {
    CAShapeLayer *shapeLayer;
    UIBezierPath *path;
}

@end

@implementation EOCKeyFrameAnimationViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor lightGrayColor];
    
    CAShapeLayer *pathLayer = [CAShapeLayer layer];
    path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(40.f, 175.f)];
    [path addCurveToPoint:CGPointMake(300.f, 175.f) controlPoint1:CGPointMake(50.f, 40.f) controlPoint2:CGPointMake(200.f, 300.f)];
    pathLayer.path = path.CGPath;
    pathLayer.lineWidth = 2.f;
    pathLayer.fillColor = [UIColor clearColor].CGColor;
    
    [pathLayer setStrokeColor:[UIColor redColor].CGColor];
    [self.view.layer addSublayer:pathLayer];
    
    shapeLayer = [CAShapeLayer layer];
    shapeLayer.contents = (__bridge id)[UIImage imageNamed:@"plane.png"].CGImage;
    shapeLayer.bounds = CGRectMake(0.f, 0.f, 50.f, 50.f);
    shapeLayer.position = CGPointMake(40.f, 175.f);
    [self.view.layer addSublayer:shapeLayer];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    
    anim.duration = 4.f;
    anim.repeatCount = 1;
    anim.rotationMode = kCAAnimationRotateAuto;
    // anim.autoreverses = YES;
    anim.calculationMode = kCAAnimationCubicPaced;  //如果用keyTimes和values,会卡顿着走,用这个平滑一些
    anim.path = path.CGPath;
    
    anim.keyTimes = @[@0.4, @0.7, @0.9];
    anim.values = @[[NSValue valueWithCGPoint:CGPointMake(60, 200)], [NSValue valueWithCGPoint:CGPointMake(100, 200)], [NSValue valueWithCGPoint:CGPointMake(140, 200)]];
    
    [shapeLayer addAnimation:anim forKey:nil];
}
@end

四. CAAnimationGroup 动画组

CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行。

属性解析:

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

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

例如,平移、旋转、缩放作为一组动画一起执行,代码如下:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
     // 平移动画
     CABasicAnimation *a1 = [CABasicAnimation animation];
     a1.keyPath = @"transform.translation.y";
     a1.toValue = @(200);
     // 缩放动画
     CABasicAnimation *a2 = [CABasicAnimation animation];
     a2.keyPath = @"transform.scale";
     a2.toValue = @(0.3);
     // 旋转动画
     CABasicAnimation *a3 = [CABasicAnimation animation];
     a3.keyPath = @"transform.rotation";
     a3.toValue = @(M_PI_2);

     // 组动画
     CAAnimationGroup *groupAnima = [CAAnimationGroup animation];

     groupAnima.animations = @[a1, a2, a3];

     //设置组动画的时间
     groupAnima.duration = 2;
//     groupAnima.fillMode = kCAFillModeForwards;
//     groupAnima.removedOnCompletion = NO;

     [self.customView.layer addAnimation:groupAnima forKey:nil];
}
效果如下: CAAnimationGroup.gif

五. CATransition

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

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

属性解析:

type://动画过渡类型
subtype://动画过渡方向
startProgress://动画起点(在整体动画的百分比)
endProgress://动画终点(在整体动画的百分比) 

合法的转场动画类型有:

fade://默认,faker淡出,layer淡入
moveIn://layer移入覆盖faker
push://layer推入,faker推出
reveal://覆盖在layer上面的faker被移出

私有(被苹果ban了,不建议直接使用):

cube://立方体旋转,layer将会在呈现的面,faker在不可见的面
suckEffect://覆盖在layer上面的faker被抽离
oglFlip://将背面的layer翻转到前面,faker翻转到背面、、
rippleEffect://伴随着水面波动动画,faker淡出,layer淡入
pageCurl://翻到下一页,faker被翻走,呈现layer
pageUnCurl://翻回上一页,layer被翻回并覆盖faker
cameraIrisHollowOpen://下面这两个是特殊的。镜头开,同时呈现部分为透明,而不是layer
cameraIrisHollowClose://类似上面,镜头关

subtype:

//4个子类型,表示上左下右4个转场动画方向
fromTop
fromLeft
fromBottom
fromRight

示例:

利用转场动画做立体翻页效果,代码如下:

- (void)leftBtnClicked {
    self.index--;
    if (self.index<1) {
        self.index=5;
    }
    self.iconView.image=[UIImage imageNamed: [NSString stringWithFormat:@"%d.png",self.index]];
    [self addAnimWithLeft:YES];
}

- (void)rightBtnClicked {
    self.index++;
    if (self.index>5) {
        self.index=1;
    }
    self.iconView.image=[UIImage imageNamed: [NSString stringWithFormat:@"%d.png",self.index]];
    [self addAnimWithLeft:NO];
}

- (void)addAnimWithLeft:(BOOL)isleft {
    //创建核心动画
    CATransition *ca = [CATransition animation];
    //告诉要执行什么动画
    //设置过度效果
    ca.type = @"cube";
    //设置动画的过度方向(向左)
    ca.subtype = isleft ? kCATransitionFromLeft : kCATransitionFromRight;
    //设置动画的时间
    ca.duration = 2.0;
    //设置动画的起始位置  比如翻页功能就能设置从哪里开始翻页
    //    ca.startProgress = 0.3;
    //    //设置动画的结束位置
    //    ca.endProgress = 0.5;
    //添加动画
    [self.iconView.layer addAnimation:ca forKey:nil];
}
动画效果: 立体翻页.gif

其实利用UIView的动画也可以做转场动画,还简单一点呢,如下:

[UIView transitionWithView:self.iconView duration:0.5 options:UIViewAnimationOptionTransitionCurlUp animations:^{
        //转场代码
        self.index = self.index + 1;
        if (self.index > 5) {
            self.index = 1;
        }
        NSString *imageName = [NSString stringWithFormat:@"%d.png",self.index];
        self.iconView.image = [UIImage imageNamed:imageName];
    } completion:^(BOOL finished) {
    }];
动画效果: UIView转场动画.gif

六. 动画时间

下面使用一个小案例介绍下动画时间,主要掌握这三个参数:beginTime speed timeOffset

效果图: 动画时间.gif

代码:

#import "EOCCAMediaTimingViewController.h"

@interface EOCCAMediaTimingViewController ()<CAAnimationDelegate> {
    
    CAShapeLayer *shapeLayer;
    int i; //是否暂停
    CFTimeInterval pausedTime; //暂停时间
    
}

@end

@implementation EOCCAMediaTimingViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor lightGrayColor];
    i = 0;
    
    //1.跟时间相关的要掌握:beginTime  speed  timeOffset
    
    shapeLayer = [CAShapeLayer layer];
    shapeLayer.contents = (__bridge id)[UIImage imageNamed:@"plane.png"].CGImage;
    shapeLayer.speed = 0.f; //默认1
    shapeLayer.bounds = CGRectMake(0.f, 0.f, 50.f, 50.f);
    shapeLayer.position = CGPointMake(25.f, 100.f);
    
    [self.view.layer addSublayer:shapeLayer];
    
    CAShapeLayer *lineLayer = [CAShapeLayer layer];
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(25.f, 100.f)];
    [path addLineToPoint:CGPointMake(300.f, 300.f)];
    lineLayer.lineWidth = 2.f;
    lineLayer.strokeColor = [UIColor redColor].CGColor;
    lineLayer.path = path.CGPath;
    [self.view.layer addSublayer:lineLayer];
    
    //CAKeyframeAnimation使用UIBezierPath当做路径,优先使用路径而不是values
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    anim.duration = 10.f;
//    anim.beginTime = CACurrentMediaTime() + 5.f;  //让动画推迟5s执行,默认马上执行
    anim.path = path.CGPath;
//    anim.autoreverses = YES; //动画是否原路返回回到起点,默认是NO
//    anim.timeOffset = 3.f;
    [shapeLayer addAnimation:anim forKey:nil];
    
    //新建UISlider, 改变timeOffset
    UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(100.f, 300.f, 200.f, 30.f)];
    slider.minimumValue = 0.f;
    slider.maximumValue = 10.f;
    [slider addTarget:self action:@selector(sliderAction:) forControlEvents:UIControlEventValueChanged];
    [self.view addSubview:slider];
    
}

- (void)sliderAction:(UISlider *)slider {
    shapeLayer.timeOffset = slider.value;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // t(本层的时间) = (tp(父层的时间) - begin) * speed + timeOffset
    if (0 == i) {   //暂停
        pausedTime = [shapeLayer convertTime:CACurrentMediaTime() fromLayer:nil];
        shapeLayer.speed = 0.f;
        shapeLayer.timeOffset = pausedTime;
    } else if (1 == i) {      //启动
        //  t = (tp - begin) * speed + offset
        shapeLayer.speed = 1.f;
        shapeLayer.beginTime = CACurrentMediaTime();
    }
    I++;
    i %= 2;
}
@end

七. 综合动画案例

效果图: 综合案例.gif

代码:

#import "EOCAnimationCaseView.h"

@interface EOCAnimationCaseView () {
        CAShapeLayer *topLineLayer;
        CAShapeLayer *leftLineLayer;
        CAShapeLayer *bottomLineLayer;
        CAShapeLayer *rightLineLayer;
}
@end

@implementation EOCAnimationCaseView

#define firstColor    [UIColor colorWithRed:90 / 255.0 green:200 / 255.0 blue:200 / 255.0 alpha:1.0]
#define secondColor   [UIColor colorWithRed:250 / 255.0 green:85 / 255.0 blue:78 / 255.0 alpha:1.0]
#define thirdColor    [UIColor colorWithRed:92 / 255.0 green:201 / 255.0 blue:105 / 255.0 alpha:1.0]
#define fourthColor   [UIColor colorWithRed:253 / 255.0 green:175 / 255.0 blue:75 / 255.0 alpha:1.0]

- (instancetype)init
{
    if (self = [super init]) {
        self.frame = CGRectMake(100.0f, 200.0f, 80.0f, 80.0f);
        self.backgroundColor = [UIColor lightGrayColor];
        [self createLayer];
    }
    return self;
}

- (void)createLayer {
    //新建四条不同颜色的线
    CGPoint topPoint = CGPointMake(3*80.f/4, 5.f);
    CGPoint leftPoint = CGPointMake(5.f, 80.f/4);
    CGPoint bottomPoint = CGPointMake(80.f/4, 80.f-5.f);
    CGPoint rightPoint = CGPointMake(80.f-5.f, 3*80.f/4);
    
    CGFloat lineLength = 80.f - 2*5.f;
    
    topLineLayer = [self createLineLayer:topPoint toPoint:CGPointMake(topPoint.x , topPoint.y + lineLength) withColor:firstColor];
    leftLineLayer = [self createLineLayer:leftPoint toPoint:CGPointMake(leftPoint.x + lineLength , leftPoint.y) withColor:secondColor];
    bottomLineLayer = [self createLineLayer:bottomPoint toPoint:CGPointMake(bottomPoint.x , bottomPoint.y - lineLength) withColor:thirdColor];
    rightLineLayer = [self createLineLayer:rightPoint toPoint:CGPointMake(rightPoint.x - lineLength , rightPoint.y) withColor:fourthColor];
    
    [self.layer addSublayer:topLineLayer];
    [self.layer addSublayer:leftLineLayer];
    [self.layer addSublayer:bottomLineLayer];
    [self.layer addSublayer:rightLineLayer];
    
    self.transform = CGAffineTransformMakeRotation(-M_PI/6);
}

- (CAShapeLayer *)createLineLayer:(CGPoint)fromPoint toPoint:(CGPoint)toPoint withColor:(UIColor *)color {
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    [path moveToPoint:fromPoint];
    [path addLineToPoint:toPoint];
    
    CAShapeLayer *layer = [CAShapeLayer layer];
    layer.lineCap = kCALineCapRound;
    layer.strokeColor = color.CGColor;
    layer.lineWidth = 10.f;
    layer.opacity = 0.8f;
    
    layer.path = path.CGPath;
    return layer;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self createAnimations];
}

- (void)createAnimations {
    
    //自身旋转
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    anim.toValue = @(M_PI*2-M_PI/6);
    anim.duration = 3.5f;
    anim.repeatDuration = 7.f;
    [self.layer addAnimation:anim forKey:nil];
    
    //让线条缩短
    [self createLineLayerAnim:topLineLayer pointX:0.f pointY:15.f];
    [self createLineLayerAnim:leftLineLayer pointX:15.f pointY:0.f];
    [self createLineLayerAnim:bottomLineLayer pointX:0.f pointY:-15.f];
    [self createLineLayerAnim:rightLineLayer pointX:-15.f pointY:0.f];
    
    //此时停止了旋转,让线条变长
    CABasicAnimation *anim1 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    anim1.fillMode = kCAFillModeForwards;
    anim1.removedOnCompletion = NO;
    anim1.beginTime = CACurrentMediaTime() + 7.f;
    anim1.fromValue = @0;
    anim1.toValue = @1;
    anim1.duration = 3.f;
    [topLineLayer addAnimation:anim1 forKey:nil];
    [leftLineLayer addAnimation:anim1 forKey:nil];
    [bottomLineLayer addAnimation:anim1 forKey:nil];
    [rightLineLayer addAnimation:anim1 forKey:nil];
    
    //self变大
    CAKeyframeAnimation *anim3 = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
    anim3.duration = 8.0f;
    anim3.beginTime = CACurrentMediaTime()+7.0f;
    NSValue *fromValue = @1;
    NSValue *toValue = @1.2;
    
    anim3.keyTimes = @[@0.1, @0.4, @0.5];
    anim3.values = @[fromValue, toValue, fromValue];
    [self.layer addAnimation:anim3 forKey:nil];
}

- (void)createLineLayerAnim:(CAShapeLayer *)layer pointX:(CGFloat)pointx pointY:(CGFloat)pointy {
    
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    anim.fillMode = kCAFillModeForwards;
    anim.removedOnCompletion = NO;
    anim.fromValue = @1;
    anim.toValue = @0;
    anim.duration = 3.f;
    [layer addAnimation:anim forKey:nil];
    
    //让这个点前后有位移
    CAKeyframeAnimation *anim1 = [CAKeyframeAnimation animation];
    anim1.keyPath = @"transform";  //课堂上这里是transform.translation 所以没效果,因为我的fromValue值设置的就是transform的值
    anim1.repeatDuration = 4.0f;
    anim1.beginTime = CACurrentMediaTime()+3.0f;
    anim1.duration = 1.0f;
    NSValue *fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, 0, 0.f)];
    NSValue *toValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(pointx, pointy, 0.f)];
    anim1.values = @[fromValue, toValue, fromValue, toValue, fromValue];
    [layer addAnimation:anim1 forKey:nil];
}
@end

Demo地址:核心动画
Demo地址:高级动画二

上一篇下一篇

猜你喜欢

热点阅读