iOS动画-Core Animation 详解 (+demo)

基本概念

一、什么是核心动画

Core Animation(核心动画)是一组功能强大、效果炫酷的动画API,无论在iOS系统或者在你开发的App中,都有大量应用。

Core Animation位于AppKit和UIKit之下,并紧密集成到Cocoa和Cocoa Touch的视图工作流中。当然,Core Animation也具有接口,可以扩展应用视图所显示的功能,并让您更好地控制应用的动画。


core Animation所在的位置


  可以看到,核心动画位于UIKit的下一层,相比UIView动画,它可以实现更复杂的动画效果。

二、UIView 和 CALayer 的关系

UIView本身完全是由CoreAnimation来实现. 真正的绘图部分, 是由一个CALayer类来管理. UIView更像是一个CALayer的管理器, 所以访问它的与绘图和坐标相关的属性, 如frame, bounds等, 实际上都是在访问其所包含的CALayer的相关属性. 因此, 可以在所有UIView的子类上实现动画效果. UIView继承自UIResponder, 能接收并响应事件, 负责显示内容的管理。

简言之:layer负责绘制内容,view负责显示和交互。

总体来说核心动画的优点有:

1)性能强大,使用硬件加速,可以同时向多个图层添加不同的动画效果
2)运行在后台线程中,在动画过程中可以响应交互事件(UIView动画默认动画过程中不响应交互事件)。

核心动画的层次关系:


核心动画的层次关系

从这个层次关系中可以看出如下几个特点:

1、CAAnimation 相关的时间控制属性、速度属性、重复性控制属性等属性都是来之于协议 CAMediaTiming ,CAAnimation 实现了这个协议,所有它的子类都具有这些属性。

2、CAAnimationGroup( 动画组)、CAPropertyAnimation、CATransition(转场动画)直接继承与CAAnimation。动画组和转场动画都可以直接使用,CAPropertyAnimation 一般不直接使用。

3、CABasicAnimation、CAKeyFrameAnimation 继承于CAPropertyAnimation,是我们最常使用的核心动画。其实CABasicAnimation就是特殊的CAKeyFrameAnimation 动画(就是具有开始状态和结束状态的CAKeyFrameAnimation 动画)。

4、CASpringAnimation 是 CABasicAnimation的子类,是iOS9.0之后新增的类,它实现了弹簧效果的动画。

三、核心动画使用步骤

1.初始化CAAnimation对象

一般使用animation方法生成实例

+ (instancetype)animation;

如果是CAPropertyAnimation的子类,还可以通过animationWithKeyPath生成实例

+ (instancetype)animationWithKeyPath:(nullableNSString*)path;

2.设置动画的相关属性

设置动画的执行时间,重复性、动画速度、执行路径,keyPath的目标值,代理等等

3.动画的添加和移除

CALayer添加动画使用addAnimation方法

- (void)addAnimation:(CAAnimation*)anim forKey:(nullableNSString*)key;

调用CALayer的removeAnimation 停止动画,并且把Layer上的动画移除。

- (void)removeAnimationForKey:(NSString*)key;  //按照key移除动画
- (void)removeAllAnimations;  //移除所有动画

4、核心动画类的常用属性

keyPath:可以指定keyPath为CALayer的属性值,并对它的值进行修改,以达到对应的动画效果,需要注意的是部分属性值是不支持动画效果的。

以下是具有动画效果的keyPath:

CATransform3D Key Paths : (transform.rotation.x)
rotation.x        设置NSNumber为其值为旋转的对象,以弧度为单位,在x轴中。
rotation.y            设置NSNumber为其值为旋转的对象,以弧度为单位,在y轴中。rotation.z            设置NSNumber为其值为旋转的对象,弧度为z轴。
rotation              设置NSNumber为其值为旋转的对象,弧度为z轴。该字段与设置rotation.z字段相同。
scale.x              设置为一个NSNumber对象,其值为x轴的比例因子。
scale.y              设置为一个NSNumber对象,其值为y轴的比例因子。
scale.z              设置为一个NSNumber对象,其值为z轴的比例因子。
scale                  设置为一个NSNumber对象,其值是所有三个比例因子的平均值。translation.x      设置为一个NSNumber对象,其值是沿x轴的平移因子。
translation.y      设置为一个NSNumber对象,其值为沿y轴的平移因子。
translation.z      设置为一个NSNumber对象,其值是沿z轴的平移因子。     
translation          设置为NSValue包含NSSize或CGSize数据类型的对象。该数据类型表示要在x和y轴上转换的数量。

CGPoint Key Paths :
position.x
position.y
position

CGRect Key Paths :
bounds.size.width
bounds.origin.x
bounds.origin.y
bounds.origin                        矩形的起始点CGPoint
bounds.size.width
bounds.size.height
bounds.size                            矩形的大小为CGSize。

opacity
backgroundColor
cornerRadius
borderWidth
contents
shadowColor
shadowOffset
shadowOpacity
shadowRadius
strokeStart                            CAShapeLayer 中的属性
strokeEnd                            CAShapeLayer 中的属性
lineWidth                              CAShapeLayer 中的属性
path

duration:动画的持续时间
repeatCount:动画的重复次数
timingFunction:动画的时间节奏控制

timingFunctionName的enum值如下:

kCAMediaTimingFunctionLinear 匀速   
kCAMediaTimingFunctionEaseIn 慢进快出(匀加速快出)
kCAMediaTimingFunctionEaseOut 快进慢出(匀加速快进)
kCAMediaTimingFunctionEaseInEaseOut 慢进慢出  (中间快)
kCAMediaTimingFunctionDefault 默认值(慢进慢出)

fillMode:填充模式视图在非Active时的行为

kCAFillModeForwards        向前填充(保持动画结束后的状态)
kCAFillModeBackwards    向后填充(保持动画开始前的状态)
kCAFillModeBoth              既向前填充又向后填充
kCAFillModeRemoved      动画结束后,移除动画

removedOnCompletion:动画执行完毕后是否从图层上移除,默认为YES(视图会恢复到动画前的状态),可设置为NO(图层保持动画执行后的状态,前提是fillMode设置为kCAFillModeForwards)

beginTime:动画延迟执行时间(通过CACurrentMediaTime() + your time 设置)

delegate:代理

CAAnimationDelegate 有两个代理方法如下: 

- (void)animationDidStart:(CAAnimation*)anim;//动画开始
- (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag;//动画结束

有关核心动画的时间控制,推荐大家看看:核心动画的时间控制 这篇文章写的非常到位。

接下来针对各种动画举例说明使用方法

CABasicAnimation

CABasicAnimation动画中,执行动画过程的属性就是fromValue, toValue。keyPath 指定什么属性需要做动画。CABasicAnimation就是特殊的关键帧动画,可以看成关键帧的values属性就是fromValue 和 toValue这两个值。

下面以改变圆角边cornerRadius和背景色backgroundColor举例:

- (void)setCRWithBasicAnimation {

        CALayer *layer = [CALayer layer];
        layer.frame = CGRectMake(30, 40, self.containerView.bounds.size.width - 30 *2,  self.containerView.bounds.size.height - 40*2);

        layer.backgroundColor = UIColor.brownColor.CGColor;   
        [self.containerView.layer addSublayer:layer];
        CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];   
        basicAnimation.fromValue=@(0);
        basicAnimation.toValue=@(50);
        basicAnimation.duration=2.f;
        basicAnimation.fillMode = kCAFillModeForwards;
        basicAnimation.removedOnCompletion=NO;        [layeraddAnimation:basicAnimationforKey:@"basicAni"];
}

圆角边动效图:

cornerRadius.gif

背景色动效图:

backgroundColor.gif

CAKeyFrameAnimation

与CABasicAnimation最大的区别在于:可以设定除了“起始点”和“终点”外的 中间关键点(可以设置多个),每一帧都对应一段时间,动画会按照values和keyTimes的设置运动。

CAKeyFrameAnimation的重要属性:

values:关键帧数组对象,里面每一个元素即为一个关键帧,动画会在对应的时间段内,依次执行数组中每一个关键帧的动画。
path:动画路径对象,可以指定一个路径,在执行动画时路径会沿着路径移动,Path在动画中只会影响视图的Position。
keyTimes:设置关键帧对应的时间点,范围:0-1。keyTimes里的值必须是升序设置。因为对应于values里关键帧所设置的,所以时间点是升序的。values 的每一个对象都对应于keyTimes里的一个时间点。 它们是一一对应的关系。如果没有设置该属性,则每一帧的时间平分。

使用keyFrameAnimation 改变 layer的rotation

- (void)changeRotationWithKeyAnimation {

    CALayer *layer = [CALayer layer];
    layer.frame = CGRectMake(100, (self.containerView.bounds.size.height - 100)/2.0,    self.containerView.bounds.size.width - 100*2,  100);
    layer.backgroundColor = [UIColor colorWithRed:0.4 green:0.8 blue:0.1 alpha:1].CGColor;       
    [self.containerView.layer addSublayer:layer];

    CAKeyframeAnimation *keyFrameAni = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
    keyFrameAni.values=@[@(M_PI_4),@(M_PI_2),@(-M_PI),@(1.5*M_PI)];
    keyFrameAni.keyTimes=@[@0,@0.35,@0.75,@1];
    keyFrameAni.duration=3.5f;
    keyFrameAni.fillMode = kCAFillModeBoth;
    keyFrameAni.removedOnCompletion=NO;
    [layeraddAnimation:keyFrameAniforKey:@"kfAnimationRotation"];
}


rotation 旋转效果图:


rotation.gif

使用path属性结合贝塞尔曲线做椭圆运动

- (void)changePathWithKeyAnimation {

    CALayer *layer = [CALayer layer];
    layer.frame=CGRectMake(80, (self.containerView.bounds.size.height-40)/2.0,40,  40);              [self.containerView.layer addSublayer:layer];

    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(20, 40, ScreenW - 40, ScreenH - 300)];

    CAKeyframeAnimation *keyFrameAni = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    keyFrameAni.path= path.CGPath;
    keyFrameAni.duration=2.0f;
    keyFrameAni.repeatCount=1000;
    [layeraddAnimation:keyFrameAniforKey:@"kfAnimation"];
}

path做椭圆运动效果图:


path椭圆运动.gif

CASpringAnimation

CASpringAnimation是iOS9新加入动画类型,是CABasicAnimation的子类,用于实现弹簧动画。

CASpringAnimation的重要属性:

mass:                  质量(质量越大,弹簧惯性越大,运动的幅度越大)
stiffness:              弹性(劲度)系数(弹性系数越大,弹簧的运动越快)
damping:            阻尼系数(阻尼系数越大,弹簧的停止越快)
initialVelocity:      初始速率(弹簧动画的初始速度大小,弹簧运动的初始方向与初始速率的正负一致,若初始速率为0,表示忽略该属性)
settlingDuration: 结算时间(根据动画参数估算弹簧开始运动到停止的时间,动画设置的时间最好根据此时间来设置)

CASpringAnimation和UIView的SpringAnimation对比:

1.CASpringAnimation 可以设置更多影响弹簧动画效果的属性,可以实现更复杂的弹簧动画效果,且可以和其他动画组合。
2.UIView的SpringAnimation实际上就是通过CASpringAnimation来实现。

使用CASpringAnimation制作弹框缩放效果:

- (void)springAnimation {

    CALayer *layer = [CALayer layer];
    layer.frame = CGRectMake(50, (self.containerView.bounds.size.height - 180)/2.0,        self.containerView.bounds.size.width - 50*2,  180);
    [self.containerView.layer addSublayer:layer];

    CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:@"transform.scale"];   
    springAnimation.fromValue=@(0.95);
    springAnimation.toValue=@(1);
    springAnimation.mass=1;//质量越大,惯性越大
    springAnimation.stiffness=50;//劲度系数,值越大,运动的越快                  springAnimation.damping=10;//阻尼系数 值越大 停止的越快                        springAnimation.initialVelocity=50;//初始速度
    springAnimation.duration= springAnimation.settlingDuration;                          [layeraddAnimation:springAnimationforKey:@"springAnimation"];
}

弹框缩放动画效果:


(弹簧效果)transform.scale.gif

CAAnimationGroup

animationGroup动画组,顾名思义就是动画的组合。它可以实现多个动画同时添加在一个layer上的功能。并且是并发执行的。没有先后顺序之分。

主要属性animations:是一个动画数组,用来接收需要添加到动画组中的核心动画。
还有一点需要注意的是:动画组的duration 需要设置的时间需要比它里包含的所有单个动画的duration要大或则等于,否则肯定有动画没有做完,然后动画组就结束了。

使用CAAnimationGroup 做一个 位移+背景变换 + 缩放的弹簧效果

- (void)groupAnimation {

    CALayer *layer = [CALayer layer];

    layer.frame = CGRectMake(50, (self.containerView.bounds.size.height - 180)/2.0, self.containerView.bounds.size.width - 50*2,  180);
    layer.cornerRadius=20;
    layer.backgroundColor = [UIColor colorWithRed:0.9 green:0.8 blue:0.1 alpha:1].CGColor;    [self.containerView.layer addSublayer:layer];

    CABasicAnimation *basicAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    basicAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(layer.position.x, ScreenH + layer.bounds.size.height/2.0)];
    basicAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(layer.position.x, layer.bounds.size.height/2.0 + 80)];
    basicAnimation.duration=2.f;
    basicAnimation.fillMode = kCAFillModeForwards;
    basicAnimation.removedOnCompletion=NO;

    CABasicAnimation *basicAnimation1 = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
    basicAnimation1.fromValue = (id)[UIColor colorWithRed:0.6 green:0.1 blue:0.5 alpha:1].CGColor;
    basicAnimation1.toValue = (id)[UIColor colorWithRed:0.1 green:0.6 blue:0.7 alpha:1].CGColor;
    basicAnimation1.duration=2.0f;
    basicAnimation1.fillMode = kCAFillModeForwards;
    basicAnimation1.removedOnCompletion=NO;

    CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:@"transform.scale"];
    springAnimation.fromValue=@(0.95);
    springAnimation.toValue=@(1);
    springAnimation.mass=1;//质量越大,惯性越大
    springAnimation.stiffness=50;//劲度系数,值越大,运动的越快    springAnimation.damping=10;//阻尼系数 值越大 停止的越快        springAnimation.initialVelocity=50;//初始速度
    springAnimation.duration= springAnimation.settlingDuration;    springAnimation.beginTime=2.3f;
    springAnimation.fillMode = kCAFillModeForwards;
    springAnimation.removedOnCompletion=NO;

    CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
    groupAnimation.duration= springAnimation.settlingDuration+2.0f;
    //这个里面的动画,没有先后顺序,可以设置beginTime动画组里的动画有先后顺序   
    groupAnimation.animations=@[springAnimation, basicAnimation,basicAnimation1];
    groupAnimation.fillMode = kCAFillModeForwards;    groupAnimation.removedOnCompletion=NO;   
    [layeraddAnimation:groupAnimationforKey:@"groupAnimation"];
}

组合动画效果图


transition+backgroundColor+scale.gif

CATransition

过渡动画/转场动画,比UIVIew转场动画具有更多的动画效果,比如Nav的默认Push视图的效果就是通过CATransition的kCATransitionPush类型来实现。

重要属性如下

startProgress:  动画的开始位置
endProgress:    动画的结束位置

type:                动画的类型

type的enum值如下:
kCATransitionFade 渐变
kCATransitionMoveIn 覆盖
kCATransitionPush 推出
kCATransitionReveal 揭开

subtype:          动画的方向

subtype的enum值如下:
kCATransitionFromRight 从右边
kCATransitionFromLeft 从左边
kCATransitionFromTop 从顶部
kCATransitionFromBottom 从底部

CATransition 实现两张图片渐变效果:

- (void)imageTransition {

      UIImageView *img1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"meinv1.jpeg"]];
      img1.frame = CGRectMake(0, 50, self.containerView.bounds.size.width,  (self.containerView.bounds.size.height - 100));
      img1.contentMode = UIViewContentModeScaleAspectFill;                                                [self.containerViewaddSubview:img1];

    UIImageView *img2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"meinv2.jpeg"]];
    img2.frame = CGRectMake(0, 50, self.containerView.bounds.size.width,  (self.containerView.bounds.size.height - 100));
    img2.contentMode = UIViewContentModeScaleAspectFill;      [self.containerViewaddSubview:img2];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        CATransition *transition = [CATransition animation];
        transition.startProgress=0.0f;
        transition.endProgress=1.0f;
        transition.duration=1.5f;
        transition.type=kCATransitionFade;
        transition.subtype = kCATransitionFromLeft;
        transition.fillMode = kCAFillModeBackwards;

        //向两个图层添加动画
        [img1.layeraddAnimation:transitionforKey:@"transition"];                                                [img2.layeraddAnimation:transitionforKey:@"transition"];

        //最后更改两个图层的可见性
        img1.hidden=NO;
        img2.hidden=YES;
    });
}

两张图片渐变效果图:

Fade.gif

后记

此致,core Animation 动画的基本内容全部说完了,想要掌握好核心动画,必须对CAAnimation的所有类和属性熟练掌握。再者就是需要结合实际场景把核心动画用起来,多练,多想,这样才能从简单的demo升级到可以做更多的炫酷效果。

本文demo地址:
https://github.com/AnyaWork/CAAnimationDemo

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

推荐阅读更多精彩内容