iOS核心动画(CoreAnimation)简介

App如果想被大众喜欢,漂亮的UI和精美的动画都是必不可少的,苹果虽然为UIView提供了一些常用动画,但是大部分看起来比较不错的效果都是通过操作Layer层实现的,因此了解核心动画是必要的。CoreAnimation是直接作用在CALayer上的(并非UIView上)非常强大的跨Mac OS X和iOS平台的动画处理API,Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。

介绍

先来看一下比较常见的层次图:


CoreAnimation.png

核心动画的动画类都是从CAAnimation类继承而来,先来看一下CAAnimation的层次结构:

CAAnimation.png

CAAnimation实现了CAMediaTiming协议(提供了动画的持续时间,速度,和重复计)和CAAction协议(为图层触发一个动画动作提供了提供标准化响应)。

CAPropertyAnimation是CABasicAnimation和CAKeyframeAnimation的抽象父类,CABasicAnimation为图层的属性提供修改,实现起来比较简单;CAKeyframeAnimation支持关键帧动画,你可以指定的图层属性的关键路径动画,包括动画的每个阶段的价值,以及关键帧时间和计时功能的一系列值。

CAAnimationGroup允许一系列动画效果组合在一起,并行显示动画。

CATransition提供了一个图层变化的过渡效果,它能影响图层的整个内容。动画进行的时候淡入淡出(fade)、推(push)、显露(reveal)图层的内容。这些过渡效果可以扩展到你自己定制的 Core Image 滤镜。

动画类同时定义了一个使用贝塞尔曲线来描述动画改变的时间函数。匀速时间函数(lineartiming function)速度会一直保持不变,渐缓时间函数(ease-outtiming function)则在动画接近其生命周期的时候减慢速度。

CABasicAnimation

CoreAnimation中最简单的莫过于CABasicAnimation,只需要设置fromValue和toValue即可,先来看一段简单的代码:

    CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    rotationAnimation.fromValue = [NSNumber numberWithFloat:0.0];
    rotationAnimation.toValue = [NSNumber numberWithFloat:2*M_PI];
    rotationAnimation.repeatCount = MAXFLOAT;
    rotationAnimation.duration =10;
    [self.rotationImgView.layer addAnimation:rotationAnimation forKey:@"transform.rotation"];

动画会让图片不断的旋转,效果如下:

Rotation.gif

相对应的我们也可以按照X轴和Y轴进行旋转:

    self.rotationXImgView.clipsToBounds=YES;
    self.rotationXImgView.layer.cornerRadius=CGRectGetWidth(self.rotationXImgView.frame)/2;
    
    CABasicAnimation *rotationXAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.x"];
    rotationXAnimation.fromValue = [NSNumber numberWithFloat:0.0];
    rotationXAnimation.toValue = [NSNumber numberWithFloat:2*M_PI];
    rotationXAnimation.repeatCount = MAXFLOAT;
    rotationXAnimation.duration =3;
    [self.rotationXImgView.layer addAnimation:rotationXAnimation forKey:@"transform.rotation.x"];
    
    self.rotationYImgView.clipsToBounds=YES;
    self.rotationYImgView.layer.cornerRadius=CGRectGetWidth(self.rotationYImgView.frame)/2;
    
    CABasicAnimation *rotationYAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
    rotationYAnimation.fromValue = [NSNumber numberWithFloat:0.0];
    rotationYAnimation.toValue = [NSNumber numberWithFloat:2*M_PI];
    rotationYAnimation.repeatCount = MAXFLOAT;
    rotationYAnimation.duration =10;
    [self.rotationYImgView.layer addAnimation:rotationYAnimation forKey:@"transform.rotation.y"];

Scale放大效果:

    CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    scaleAnimation.fromValue = [NSNumber numberWithFloat:1.0];
    scaleAnimation.toValue = [NSNumber numberWithFloat:1.5];
    scaleAnimation.autoreverses = YES;//自动反向执行动画效果
    scaleAnimation.repeatCount = MAXFLOAT;
    scaleAnimation.duration = 0.8;
    [self.scaleImgView.layer addAnimation:scaleAnimation forKey:@"FlyElephant.scale"];
    
    CABasicAnimation *scaleXAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"];
    scaleXAnimation.fromValue = [NSNumber numberWithFloat:1.0];
    scaleXAnimation.toValue = [NSNumber numberWithFloat:1.5];
    scaleXAnimation.autoreverses = YES;//自动反向执行动画效果
    scaleXAnimation.repeatCount = MAXFLOAT;
    scaleXAnimation.duration = 0.8;
    [self.scaleXImgView.layer addAnimation:scaleXAnimation forKey:@"FlyElephant.scale.x"];
    
    CABasicAnimation *scaleYAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.y"];
    scaleYAnimation.fromValue = [NSNumber numberWithFloat:1.0];
    scaleYAnimation.toValue = [NSNumber numberWithFloat:1.5];
    scaleYAnimation.autoreverses = YES;//自动反向执行动画效果
    scaleYAnimation.repeatCount = MAXFLOAT;
    scaleYAnimation.duration = 0.8;
    [self.scaleYImgView.layer addAnimation:scaleYAnimation forKey:@"FlyElephant.scale.y"];
    
    
    CABasicAnimation *scaleZAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.z"];
    scaleZAnimation.fromValue = [NSNumber numberWithFloat:1.0];
    scaleZAnimation.toValue = [NSNumber numberWithFloat:1.5];
    scaleZAnimation.autoreverses = YES;//自动反向执行动画效果
    scaleZAnimation.repeatCount = MAXFLOAT;
    scaleZAnimation.duration = 0.8;
    [self.scaleZImgView.layer addAnimation:scaleZAnimation forKey:@"FlyElephant.scale.z"];

Translation平移效果

    CABasicAnimation *translationX=[CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
    translationX.toValue=@(200);
    translationX.duration=5;
    translationX.removedOnCompletion=NO;
    translationX.fillMode=kCAFillModeForwards;
    translationX.repeatCount=MAXFLOAT;
    translationX.autoreverses=YES;
    [self.translationXImgView.layer addAnimation:translationX forKey:@"FlyElephant.translation.x"];
    
    CABasicAnimation *translationY=[CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
    translationY.toValue=@(100);
    translationY.duration=5;
    translationY.removedOnCompletion=NO;
    translationY.fillMode=kCAFillModeForwards;
    translationY.repeatCount=MAXFLOAT;
    translationY.autoreverses=YES;
    [self.translationYImgView.layer addAnimation:translationY forKey:@"FlyElephant.translation.y"];
    

    CABasicAnimation *translation=[CABasicAnimation animationWithKeyPath:@"transform.translation"];
    translation.toValue=[NSValue valueWithCGPoint:CGPointMake(100, 100)];
    translation.duration=5;
    translation.removedOnCompletion=NO;
    translation.fillMode=kCAFillModeForwards;
    translation.repeatCount=MAXFLOAT;
    translation.autoreverses=YES;
    [self.translationImgView.layer addAnimation:translation forKey:@"FlyElephant.translation"];

CABasicAnimation设置比较简单,如果有心的话会有一个疑问,keyPath的里面设置的值是怎么找到的,怎么可以找到所有animationWithKeyPath中的keyPath需要的值呢?最后总结的时候会给出答案,如果有兴趣的可以自己先搜索一番~

CAKeyframeAnimation

CAKeyframeAnimation也被称为关键帧动画,因为它可以细化动画在运动过程中的变换的位置,路径以及时间,如果设置的比较好可以完成很多意想不到的效果,本文就简单的稍微设置一下,之后有时间会单独补充一些有意思的动画:

基于Point的变换

keyFrame.gif
    CGPoint p1=CGPointMake(self.positionImgView.center.x, self.positionImgView.center.y);
    CGPoint p2=CGPointMake(80, 100);
    CGPoint p3=CGPointMake(100, 120);
    
    CGPoint p4=CGPointMake(120, 150);
    CGPoint p5=CGPointMake(140, 160);
    NSArray *values=[NSArray arrayWithObjects:[NSValue valueWithCGPoint:p1],[NSValue valueWithCGPoint:p2],[NSValue valueWithCGPoint:p3],[NSValue valueWithCGPoint:p4],[NSValue valueWithCGPoint:p5], nil];
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    [animation setValues:values];
    [animation setDuration:10.0];
    [animation setCalculationMode:kCAAnimationCubic];
    animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    [self.positionImgView.layer addAnimation:animation forKey:@"FlyElephant.point"];

基于Path变换

keyFramePath.gif
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path,NULL,self.positionImgView.center.x,self.positionImgView.center.y);
    
    for (NSInteger i=1; i<5; i++) {
        CGPathAddLineToPoint(path, NULL, self.positionImgView.center.x+i*10, self.positionImgView.center.y);
    }
    //曲线
    CGPathAddCurveToPoint(path,NULL,50.0,275.0,150.0,275.0,70.0,120.0);
    CGPathAddCurveToPoint(path,NULL,150.0,275.0,250.0,275.0,90.0,120.0);
    CGPathAddCurveToPoint(path,NULL,250.0,275.0,350.0,275.0,110.0,120.0);
    CGPathAddCurveToPoint(path,NULL,350.0,275.0,450.0,275.0,130.0,120.0);
    
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    [animation setPath:path];
    [animation setDuration:3.0];
    // [animation setAutoreverses:YES];
    CFRelease(path);
    [self.positionImgView.layer addAnimation:animation:@"FlyElephant"];

通过代码我们发现,Path和values接收都是一个数组,而不是一个固定值,这里面我们没有设置keyTimes,下面看一个常见的抖动效果:

Shake.gif
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position.x";
    animation.values = @[ @0, @20, @-20, @20, @0 ];
    animation.keyTimes = @[ @0, @(1 /8.0), @(1/ 2.0), @(7/ 8.0), @1 ];
    animation.duration =0.5;
    animation.additive = YES;
    [self.textField.layer addAnimation:animation forKey:@"FlyElephant.Shake"];

CAAnimationGroup

CAAnimationGroup被称为动画组,动画组可以将各种不同的动画同时执行,如果你想,你就可以设置千变万化的效果,下面简单的将CABasicAnimation和CAKeyframeAnimation结合在一起做一个简单的动画效果:

Group.gif
    CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    scaleAnimation.fromValue = [NSNumber numberWithFloat:1.0];
    scaleAnimation.toValue = [NSNumber numberWithFloat:2.0];
    NSMutableArray *arr=[NSMutableArray array];
    for (NSInteger i=0; i<5; i++) {
         CGPoint p=CGPointMake(self.groupImgView.center.x+i*20, self.groupImgView.center.y+i*20);
        [arr addObject:[NSValue valueWithCGPoint:p]];
    }
    NSArray *values=arr;
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    [animation setValues:values];
    animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    
    CAAnimationGroup *animationGroup=[CAAnimationGroup animation];
    animationGroup.animations=@[scaleAnimation,animation];
    animationGroup.duration=5.0f;
    animationGroup.autoreverses=YES;
    [self.groupImgView.layer addAnimation:animationGroup forKey:@"FlyElephant.AnimationGroup"];

这个算是基本动画,需求不一样,设置的效果就不一样~

AnimationWithKeyPath的值

最开始做动画一般都会对keyPath这个值莫名其妙,因为它不是常量,需要变换的时候找不到对应的需要设置的值,如果你在网上搜索,很可能看到的是这张图:

Snip20160408_1.png

下面这张图你基本上是找不到的,如下:

Snip20160408_2.png

这两张图涵盖所有你需要设置的keypath,两张图都是截取自苹果官网,自己当时找的时候也发费了一番功夫,参考链接如下:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/AnimatableProperties/AnimatableProperties.html#//apple_ref/doc/uid/TP40004514-CH11-SW2
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreAnimation_guide/Key-ValueCodingExtensions/Key-ValueCodingExtensions.html#//apple_ref/doc/uid/TP40004514-CH12-SW2

Swift 抖动效果

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

推荐阅读更多精彩内容