iOS动画-通过CALayer让你的爱啪啪(APP)动起来

一、iOS动画

iOS中实现一个动画十分简单,在view层面上通过调用

[UIView animateWithDuration:duration animations:^{
        //执行动画
 }]

但是它不能控制动画的暂停和组合,所以就需要用到CoreAnimation了。
iOS中的动画主要分为:基础动画(CABasicAnimation)、关键帧动画(CAKeyFrameAnimation)、动画组(CAAnimationGroup)、转场动画(CATransition),关系图如下

动画关系图
  • CAAnimation:核心动画的基础类,不能直接使用,负责动画运行时间、速度控制,本身是实现了CAMediaTiming协议
  • CAPropertyAnimation:属性动画的基类,即通过属性进行动画设置,不能直接使用。
  • CAAnimationGroup:动画组,可以通过动画组来进行所有动画行为的统一控制,组中所有动画可以并发执行。
  • CATransition:转场动画,主要通过滤镜进行动画效果设置。
  • CABasicAnimation:基础动画,通过修改属性进行动画参数控制,只有初始状态和结束状态。
  • CAKeyFrameAnimation:关键帧懂的规划,通过修改属性进行动画,可以有多个状态控制。

二、简单动画的实现(CABasicAnimation)

You can perform simple animations implicitly or explicitly depending on your needs. Implicit animations use the default timing and animation properties to perform an animation, whereas explicit animations require you to configure those properties yourself using an animation object. So implicit animations are perfect for situations where you want to make a change without a lot of code and the default timing works well for you.

apple提供隐式动画和显式动画两种,隐式动画使用系统默认的时间和属性,如果要显式动画,需要用户自己配置属性。当用户想要用少量代码和默认的时间实现简单的动画,用隐式就行了。

什么是隐式动画,显式动画?
  • 隐式动画就是不需要手动调用动画方法,系统默认提供的动画效果,时间是1/4秒,root layer没有隐式动画。当你改变了layer的属性,apple会立刻把这个属性改变,但是会通过呈现树以动画的形式展示改变的效果(这也就是为什么apple建议通过presentationLayer的属性来访问当前屏幕上的layer信息了),这个动态改变的过程就是隐式动画。
  • 显式动画是开发者自定义的动画效果,可以设置属性改变的时间长度,改变的效果(淡入淡出等)。

2.1 单个动画

实现简单动画通常都用CABasicAnimation对象和用户配置好的动画参数来实现,通过调用basicAni的addAnimation:forKey:方法将动画添加到layer里面,这个方法会根据填入的key值来决定让哪个属性进行动画。

图层动画的本质就是将图层内部的内容转化为位图经硬件操作形成一种动画效果,其实图层本身并没有任何的变化
也就是说动画不会从根本上改变layer的属性,只是展示属性变化的效果

简单动画会在一定时间内通过动画的形式改变layer的属性值,比如用动画效果将透明度opacity从1到0:
以透明度为例实现简单动画:

CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:@"opacity"];
fade.fromValue = [NSNumber numberWithFloat:1.0];
fade.toValue = [NSNumber NumberWithFloat:0.0];
fade.duration = 1.0;
//注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取
[self.layer addAnimation:fade forKey:@"opacity];
//动画不会真正改变opacity的值,在动画结束后要手动更改
self.opacity = 0;

还可以通过animation 的delegate方法监测动画是否执行完毕

CABasicAnimation *fade = [CABasicAnimation animationWithKeyPath:@"opacity"];
fade.fromValue = [NSNumber numberWithFloat:1.0];
fade.toValue = [NSNumber NumberWithFloat:0.0];
fade.duration = 1.0;
fade.delegate = self;
//注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取
[self.layer addAnimation:fade forKey:@"opacity];
//动画不会真正改变opacity的值,在动画结束后要手动更改

//代理方法
- (void)animationDidStart:(CAAnimation *)anim{
    NSLog(@"START");
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    //如果不通过动画事务将隐式动画关闭,会出现动画运行完毕后会从起点突变到终点。
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.layer.position = [[anim valueForKey:@"KPOINT"] CGPointValue];
    [CATransaction commit];
}
不更改透明度
更改了透明度

Tip: When creating an explicit animation, it is recommended that you always assign a value to the fromValue property of the animation object. If you do not specify a value for this property, Core animation uses the layer’s current value as the starting value. If you already updated the property to its final value, that might not yield the results you want.

显式动画,apple建议你总是给fromValue赋值,如果你不赋值给fromValue,系统会取当前的属性值作为起始点,如果你更新了最终值,可能会得到意向不到的结果(不是很明白它的意思,我的理解是如果没更改最终值,这个属性是不会被动画改变的,动画只是视觉上的效果,原因见下)。

Unlike an implicit animation, which updates the layer object’s data value, an explicit animation does not modify the data in the layer tree. Explicit animations only produce the animations. At the end of the animation, Core Animation removes the animation object from the layer and redraws the layer using its current data values.
隐式动画会直接改变该属性的值,而显式动画只是一个动画效果,Core Animation会根据属性的最终值重回layer,所有必须在动画结束之后手动更改属性值,除非你不想改变该属性。

2.2 多个动画组合

实现移动过程中旋转的动画效果:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CGPoint p = [[touches anyObject] locationInView:self.view];
    CALayer *layer = self.leftBottomView.layer;
    self.layer = layer;
    //添加动画
    [self positionLayer:layer position:p];
    [self rotate:layer];
}

-(void)rotate:(CALayer *)layer{
    CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];
    basicAnimation.duration=3.0;
    //添加动画到图层,注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取
    [layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
}
-(void)positionLayer:(CALayer *)layer position:(CGPoint)p{
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.toValue = [NSValue valueWithCGPoint:p];
    animation.duration = 3;
    animation.delegate = self;
    [animation setValue:[NSValue valueWithCGPoint:p] forKey:@"KPOINT"];
    [layer addAnimation:animation forKey:@"KPOSITION"];
}

- (void)animationDidStart:(CAAnimation *)anim{
    NSLog(@"START");
    [self.layer animationForKey:@"KPOSITION"];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    self.layer.position = [[anim valueForKey:@"KPOINT"] CGPointValue];
    [CATransaction commit];
}
多个动画结合

三、关键帧动画(CAKeyFramedAnimation)

3.1 关键帧动画简单实现

CABasicAnimation只能设定初始和最终值,动画也只能是简单的从一个值到另一个值的变化。CAKeyFramedAnimation能够通过改变多个值,让你们的动画能够以线性或者非线性的方式展现。可以把keyFramedAnimation看成是许多个basicAnimation组合而成,每两个变化值之间的动画看成一个简单动画。

以改变layer的position为例通过设置关键帧,做出曲线动画,它会根据设定的path
![Uploading keyframe_717706.gif . . .](pathCGPathRef类型),通过描绘路径进行关键帧动画控制:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CGPoint p = [[touches anyObject] locationInView:self.view];
    CALayer *layer = self.leftBottomView.layer;
    self.layer = layer;
    //开始动画
    [self keyFramed:layer];
    //动画结束之后改变layer位置
    layer.position = CGPointMake(layer.position.x, layer.position.y+200);
}

-(void)keyFramed:(CALayer *)layer{
    CAKeyframeAnimation *keyFramedAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    //创建路径
    CGMutablePathRef path = CGPathCreateMutable();
    CGFloat x = layer.position.x;
    CGFloat y = layer.position.y;
    //添加贝塞尔曲线路径
    CGPathMoveToPoint(path, nil, x, y);
    CGPathAddCurveToPoint(path, nil, x+200, y, x+200, y+100, x, y+100);
    CGPathAddCurveToPoint(path, nil, x+200, y+100, x+200, y+200, x, y+200);
    keyFramedAnimation.path = path;
    keyFramedAnimation.duration = 5;
    [layer addAnimation:keyFramedAnimation forKey:@"KEYFRAME"];
}
keyFrameAnimation

3.2 其他属性解析

  • calculationMode:

The calculationMode property defines the algorithm to use in calculating the animation timing. The value of this property affects how the other timing-related properties are used

calculationMode,动画计算模式,规定了动画时间的算法,对与时间有关的动画属性起作用。它是一个字符串类型,有如下:

//线性动画,是计算模式的默认值
kCAAnimationLinear
//离散动画,每帧之间没有过渡
kCAAnimationDiscrete
//均匀动画,会忽略keyTimes
kCAAnimationPaced
//平滑执行
kCAAnimationCubic
//平滑均匀执行
kCAAnimationCubicPaced

各个值的动画效果示意图:

calculationMode示意图
  • keyTimes:

The keyTimes property specifies time markers at which to apply each keyframe value

keyTimes用于设置每帧之间的动画执行时间,但是只有在calculationModeKCAAnimationLinearKCAAnimationDiscreteKCAAnimationCubic的时候才有效。

改变keyTimes来改变每帧的动画时间:

CAKeyframeAnimation *keyFramedAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    CGMutablePathRef path = CGPathCreateMutable();
    CGFloat x = layer.position.x;
    CGFloat y = layer.position.y;
    CGPathMoveToPoint(path, nil, x, y);
    CGPathAddCurveToPoint(path, nil, x+200, y, x+200, y+100, x, y+100);
    CGPathAddCurveToPoint(path, nil, x+200, y+100, x+200, y+200, x, y+200);
    keyFramedAnimation.path = path;
    keyFramedAnimation.duration = 5;
    keyFramedAnimation.calculationMode = kCAAnimationCubicPaced;
    keyFramedAnimation.keyTimes = @[@0.0,@0.2,@1.0];
    [layer addAnimation:keyFramedAnimation forKey:@"KEYFRAME"];
keytime

四、动画组

CABasicAnimation和CAKeyFramedAnimatio一次只能改变一个属性,显示开发中要实现的动画效果一般都是多个动画一起执行的,比如平移过程中加入旋转效果等等。通过CAAnimationGroup能让开发者自由组合多个动画效果。

动画组的实现并不复杂,只需要将各个动画创建好再添加到动画组内就行了。

来给上面的关键帧动画加上旋转效果试试:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    CGPoint p = [[touches anyObject] locationInView:self.view];
    CALayer *layer = self.leftBottomView.layer;

    CAAnimationGroup *group = [CAAnimationGroup animation];
    CAKeyframeAnimation *key = [self createKeyFramed:layer];
    CABasicAnimation *rotate = [self createRotate:layer];
    group.animations = @[key,rotate];
    [layer addAnimation:group forKey:@"GROUP"];

    layer.position = CGPointMake(layer.position.x, layer.position.y+200);
}


-(CAKeyframeAnimation *)createKeyFramed:(CALayer *)layer{
    CGFloat x = layer.position.x;
    CGFloat y = layer.position.y;

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, nil, x, y);
    CGPathAddCurveToPoint(path, nil, x+200, y, x+200, y+100, x, y+100);
    CGPathAddCurveToPoint(path, nil, x+200, y+100, x+200, y+200, x, y+200);
   
    CAKeyframeAnimation *keyFramedAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    keyFramedAnimation.path = path;
    keyFramedAnimation.duration = 5;
    [layer addAnimation:keyFramedAnimation forKey:@"KEYFRAME"];
    return keyFramedAnimation;
}

-(CABasicAnimation *)createRotate:(CALayer *)layer{
    CABasicAnimation *rotate=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    rotate.toValue =[NSNumber numberWithFloat:M_PI_2*3];
    rotate.duration =5.0;
    [layer addAnimation:rotate forKey:@"KCBasicAnimation_Rotation"];
    return rotate;
}
GROUP

官方文档给的示例代码:

// Animation 1
CAKeyframeAnimation* widthAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderWidth"];
NSArray* widthValues = [NSArray arrayWithObjects:@1.0, @10.0, @5.0, @30.0, @0.5, @15.0, @2.0, @50.0, @0.0, nil];
widthAnim.values = widthValues;
widthAnim.calculationMode = kCAAnimationPaced;
 
// Animation 2
CAKeyframeAnimation* colorAnim = [CAKeyframeAnimation animationWithKeyPath:@"borderColor"];
NSArray* colorValues = [NSArray arrayWithObjects:(id)[UIColor greenColor].CGColor,
            (id)[UIColor redColor].CGColor, (id)[UIColor blueColor].CGColor,  nil];
colorAnim.values = colorValues;
colorAnim.calculationMode = kCAAnimationPaced;
 
// Animation group
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:colorAnim, widthAnim, nil];
group.duration = 5.0;
 
[myLayer addAnimation:group forKey:@"BorderChanges"];

五、转场动画(CATransition)

转场动画会为layer的转换添加一个视觉效果,最常见的是一个layer的消失和另一个layer的出现。

4196_141022104125_1.jpg

子类型:

4196_141022104212_1.jpg
CATransition* transition = [CATransition animation];
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
transition.duration = 1.0;
 
// Add the transition animation to both layers
[myView1.layer addAnimation:transition forKey:@"transition"];
[myView2.layer addAnimation:transition forKey:@"transition"];
 
// Finally, change the visibility of the layers.
myView1.hidden = YES;
myView2.hidden = NO;

六、暂停和继续动画

在iOS动画时间是动画十分重要的一部分,Core Animation 通过调用 CAMediaTiming协议的属性和方法来精确的设定时间信息。

However, the local time of a layer can be modified by its parent layers or by its own timing parameters. For example, changing the layer’s speed property causes the duration of animations on that layer (and its sublayers) to change proportionally

每个动画都有自己的local time,用来管理动画时间信息。通常两个layer的local time是十分接近的,因此开发者不必去关心这方面的事情,但是改变了layer的speed属性的话就会导致动画的时间周期改变,从而影响各个layer动画local time。

beginTime指定了动画开始之前的的延迟时间。这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0(就是说动画会立刻执行)。

speed是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。如果2.0的速度,那么对于一个duration为1的动画,实际上在0.5秒的时候就已经完成了。

timeOffset和beginTime类似,但是和增加beginTime导致的延迟动画不同,增加timeOffset只是让动画快进到某一点,例如,对于一个持续1秒的动画来说,设置timeOffset为0.5意味着动画将从一半的地方开始。

和beginTime不同的是,timeOffset并不受speed的影响。所以如果你把speed设为2.0,把timeOffset设置为0.5,那么你的动画将从动画最后结束的地方开始,因为1秒的动画实际上被缩短到了0.5秒。然而即使使用了timeOffset让动画从结束的地方开始,它仍然播放了一个完整的时长,这个动画仅仅是循环了一圈,然后从头开始播放。

Core Animation的 马赫时间 ,可以使用CACurrentMediaTime来访问,它返回了设备自从上次启动到现在的时间(然而这并没什么卵用),它的真实作用在于对动画的时间测量提供了一个相对值。

每个CALayer和CoreAnimation都有自己的本地时间概念,系统提供了两个方法:

-(CFTimeInterval)convertTime:(CGTimeInterval)t fromLayer:(CALayer *)l;
-(CFTimeInterval)convertTime:(CGTimeInterval)t toLayer:(CALayer *)l;

配合父图层/动画图层关系中的beginTimetimeOff,speed,可以控制动画的暂停,快退/快进的功能:

用一个小demo实现暂停、继续动画功能:

- (void)pauseAnimation:(CALayer *)layer{
    CFTimeInterval pauseTime = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.timeOffset = pauseTime;
    layer.speed = 0;
}

- (void)resumeAnimation:(CALayer *)layer{
    CFTimeInterval pauseTime = [self.layer timeOffset];
    layer.timeOffset = 0;
    layer.beginTime = 0;
    layer.speed = 1;
    CFTimeInterval timeSincePause = [self.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pauseTime;
    layer.beginTime = timeSincePause;
}

- (IBAction)pause:(id)sender {
    self.stop = !self.stop;
    if (self.stop) {
        [self animationResume];
    }else{
        [self animationPause];
    }
}

得到的效果


pause

七、动画事务(CATransaction)

CATransaction负责管理动画、动画组的创建和执行。大部分情况下开发者不需要手动创建trasaction(动画事务),但如果要更精确的控制动画的属性,比如duration,timing function等,就要通过手动创建CATrasaction。

开启新的动画事务调用start方法,结束事务调用commit方法。

apple给的简单示例代码,通过:

[CATransaction begin];
theLayer.zPosition=200.0;
theLayer.opacity=0.0;
[CATransaction commit];

CATransaction通过block运行开发者在动画在动画结束之后执行一些其他操作:

[CATransaction setCompletionBlock:^{
          //执行动画结束之后的操作
    }];

CATransaction通过setValue:forKey来改变动画的属性,key值是使用的是系统提供的字符串类型值,以改变动画时间为例:

[CATransaction begin];
[CATransaction setValue:@10 forKey:kCATransactionAnimationDuration];
[CATransaction comment];

除了时间外还有:

kCATransactionDisableActions
kCATransactionAnimationTimingFunction
kCATransactionCompletionBlock

CATransaction允许一个事务内嵌套多个事务的操作,但是每个动画事务的begincommit必须配套使用。

//outer transaction
[CATransaction begin];
CATransaction setValue:@2 forKey:kCATransactionAnimationDuration];
layer.positon = CGPointMake(0,0);

//inner transaction
[CATransaction begin];
[CATranscation setValue:@3 forKey:kCATransactionAnimationDuration];
layer.zPosition = 200;
layer.opacity = 0;
//inner transaction completed
[CATransaction commit];

//outer transaction completed
[CATranscation commit]

阅读原文

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,471评论 6 30
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,104评论 5 13
  • 前言 本文只要描述了iOS中的Core Animation(核心动画:隐式动画、显示动画)、贝塞尔曲线、UIVie...
    GitHubPorter阅读 3,607评论 7 11
  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 3,082评论 1 23
  • 转载:http://www.cnblogs.com/jingdizhiwa/p/5601240.html 1.ge...
    F麦子阅读 1,536评论 0 1