iOS动画开发

一、 CoreAnimation 核心动画

CoreAnimation提供高帧速率和流畅的动画,而不会给CPU带来负担并降低应用程序的速度。绘制动画的每个帧所需的大部分工作都是为我们完成的。我们可以配置动画参数,例如起点和终点,Core Animation会完成其余工作,将大部分工作交给专用图形硬件,以加速渲染。

  • 1.核心动画基本概念:CoreAnimation是跨平台的,支持 iOS 和 MacOS。是强大的动画处理API,可以做出非常炫丽的动画效果

  • 2.提供的动画类型:

    画类型 名称
    本动画 CABasicAnimation
    键帧动画 CAKeyframeAnimation
    画组 CAAnimationGroup
    场动画 CATransition
  • 3.通常的开发步骤:
    ①初始化一个CAAnimation对象,并设置相关属性
    ②添加动画对象到层(CALayer)中,(在CALayer的属性中,有很多能实现CAAnimation的动画效果,opacity、position、rtransform、bounds、contents等)
    ③通过调用CALayer的addAnimation:forKey: 增加动画到层(CALayer)中,这样就能触发动画。通过调用removeAnimationForKey可以停止层的动画
    ④CAAnimation动画执行过程都是在后台的,不会阻塞主线程

在详细讲解Core Animation之前,先要细说一下CALayer(图层)

二、 CALayer(图层)

图层通常用于为视图提供后备存储,但也可以在没有视图的情况下使用以显示内容。图层的主要工作是管理您提供的可视内容,但图层本身具有可设置的可视属性,例如背景颜色,边框和阴影。除了管理可视内容外,该图层还维护有关其内容几何的信息(例如其位置,大小和变换),用于在屏幕上显示该内容。修改图层的属性是指如何在图层的内容或几何图形上启动动画。层对象通过采用定义层的定时信息的协议来封装层及其动画的持续时间和调步。

  • 1.简介:在iOS中,我们所看到的视图UIView是通过QuartzCore中的CALayer显示出来的,我们所需要做的动画效果也是在CALayer上完成的。UIView继承自UIResponder,主要特点是可以响应触摸事件。CALayer实际的图层内容管理。每个UIView内部都有一个Layer的属性。

  • 2.和Core Animation的关系:图层类是Core Animation的基础,它提供了一套抽象概念。CALayer是整个图层类的基础,它是所有核心动画图层类的父类。

  • 3.基本属性:

属性 释义
bounds 宽度和高度
position 位置(默认指中心点,具体由anchorPoint决定)
anchorPoint 锚点(x,y的范围都是0-1),决定了position的含义
backgroundColor 背景颜色(CGColorRef类型)
borderColor 边框颜色(CGColorRef类型)
borderWidth 边框宽度
cornerRadius 圆角半径
contents 内容(比如设置为图片CGImageRef)

CALayer和UIView一样都可以使用frame,但是对于动画来说,很多时候是在父视图上进行操作和实现效果但所以最好还是使用bounds和position。

  • 4.通常的开发步骤:
    // 开始创建自定义图层:
    // 1、新建图层
    newLayer = [CALayer layer];
    
    // 2、设置图层属性
    // 宽度 高度
    newLayer.bounds = CGRectMake(self.view.frame.size.width / 2 - 50, 100, 100, 100);
    // 背景颜色
    newLayer.backgroundColor = [[UIColor greenColor]CGColor];
    // 设置内容 contents 为id属性,需要强转
    newLayer.contents = (__bridge id _Nullable)([[UIImage imageNamed:@"头像1"]CGImage]);
    // 注意:因为框架不同,在设定CA类型的属性的时候,如果使用了UI框架的东西,需要转换比如CGImage、CGColor
    
    // 位置 中心点position 和 UIView的center类似
    newLayer.position = CGPointMake(200, 200);
    
    // 锚点 默认数值 0.5 0.5 位于中心点 0.0在左上角,1.1在右下角
    // 锚点是用来定义position的 [ 所在位置 ] 的
    newLayer.anchorPoint = CGPointMake(1, 1);
    // 锚点0.0 图片的frame就成为:(锚点的position.x, 锚点的position.y, w, h)
    // 锚点1.1 图片的frame就成为:(锚点的position.x - 视图宽度, 锚点的position.y - 视图高度, w, h)
    
    // 3、把图层添加到当前视图
    [self.view.layer addSublayer:newLayer];
  • 5.常用动画

①旋转动画

[view.layer setTransform:CATransform3DMakeRotation(M_PI_2, 1, 1, 1)];// 旋转角度、X\Y\Z轴
  • 1

②缩放动画

[view.layer setTransform:CATransform3DMakeScale(0.5, 2, 1)];// 旋转角度、X\Y\Z轴   
  • 1

③平移动画

[view.layer setTransform:CATransform3DMakeTranslation(0, -100, 0)];
  • 1
  • 6.演示效果:

iOS动画开发_第1张图片

  • 7.CALayer的mask属性:mask是一个layer层,并且作为背景层和组成层之间的一个遮罩层通道,默认是nil。并且如果要创建新的layer赋给mask,那么新的layer必须没有superlayer,也不支持含有子mask。mask是个介于背景层和表现层之间的一个遮罩层,如果要创建新的layer赋给这个遮罩层,则这个新layer不能有superlayer,也不能再上面添加子mask。layer的大小和形状是受到mask遮罩层的影响的,可以通过赋给mask层一个新layer,来实现改变layer形状的效果。

CALayer在开发中需要注意的是:

1.CALayer对应的View的Layer是不允许重复创建的,需要新建子CALayer层进去 调用CALayer的addsubLayer。位置和锚点这两个属性比较重要,要做好动画就需要很好的理解这儿两个属性。

2.绘制CALayer 两种方法:
①创建一个CALayer子类,然后覆盖drawInContext:方法,使用Quartz2D API进行绘图。
②设置CALayer的delegate,然后让delegate实现drawLayer:inContext方法进行绘图
⭐️不推荐CALayer绘图,因为CALayer不能响应触发事件。

三、 CAMediaTiming 协议

在制作核心动画时,还需要注意一点,就是CAMediaTiming协议。动画所有跟时间相关的属性(duration, beginTime, repeatCount等)都来自于CAMediaTiming协议,它由CABasicAnimation和CAKeyframeAnimation的父类CAAnimation实现。协议一共定义了8个属性,通过这8个属性就能完全地控制动画时间。

属性 释义
beginTime 指定接收器相对于其父对象的开始时间(如果适用)。
timeOffset 指定活动本地时间的附加时间偏移。
repeatCount 确定动画重复的次数。
repeatDuration 确定动画重复的秒数。
duration 指定动画的基本持续时间(以秒为单位)。
speed 指定时间从父时间空间映射到接收者的时间空间的方式。
autoreverses 确定接收器在完成时是否反向播放。
fillMode 确定定时对象在其活动持续时间完成后的行为方式。

这里需要注意一下 fillMode,fillMode是动画完成时动画的填充模式

属性 释义
kCAFillModeBackwards 动画完成时,接收器将值固定在零到零之前
kCAFillModeBoth 接收器钳制对象时间空间两端的值
kCAFillModeForwards 动画完成时,接收器在其最终状态下保持可见
kCAFillModeRemoved 动画完成后,接收器将从演示文稿中删除

好了,前期工作我们就准备完成了,那么现在我们开始愉快的作动画吧


华丽的分割线


四、 CoreAnimation 动画效果

1.基本动画 CABasicAnimation

  • 创建一个平移动画:
    //   ①.实例化动画对象
    CABasicAnimation * animate = [CABasicAnimation animationWithKeyPath:@"position"];
    /**
         初始化的时候使用keypath,我们如何找到keypath呢?如下
         self.view.layer.position; 这些都是keypath
         self.view.layer.opacity;
    */    
    
    //   ②.设置动画属性
    //   ②.1开始数值fromValue
    [animate setFromValue:[NSValue valueWithCGPoint:CGPointMake(10, 10)]];
    //   ②.2结束数值toValue
    [animate setToValue:[NSValue valueWithCGPoint:CGPointMake(150, 150)]];
    //   ②.3动画时长
    [animate setDuration:0.4f];
    
    //   ②.4动画结束后,视图停留在目标位置
    [animate setRemovedOnCompletion:NO];
    [animate setFillMode:kCAFillModeForwards];
    
    //   ②.5设置速度控制函数
    animate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];// 渐进效果
 
    //   ③.将动画添加到图层
    [demoView.layer addAnimation:animate forKey:nil];

Tpis1:上述代码中②.4的代码使用方式是搭配使用的,一般不设定的话,动画平移到目标点之后就会自动恢复到起点位置
Tpis2:在这要说一点,CALayer自身是带有隐式动画的,设定好起点和中点之后,自动展示默认移动动画效果。

效果如下:
iOS动画开发_第2张图片

  • 创建一个旋转动画:
    //   ①.实例化动画对象
    CABasicAnimation * animate = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];// 旋转
    /**
    	初始化的时候使用keypath,我们如何找到keypath呢?如下
    	self.view.layer setTransform:CATransform3DMakeRotation(<#CGFloat angle#>, <#CGFloat x#>, <#CGFloat y#>, <#CGFloat z#>)
    	CATransform3DMakeScale
    	CATransform3DMakeTranslation
    */   
    
    //   ②.设置动画属性
    //   ②.1设置动画无限循环
    animate.repeatCount = MAXFLOAT;
    //   ②.2设置动画数值
    [animate setToValue:@M_PI];// 旋转180度
    
    //   ③.将动画添加到图层
    // forkey 是动画名称,可以在查找动画和删除动画的时候派上用途
    [demoView.layer addAnimation:animate forKey:@"viewRotationAnimate"];

Tips:每次一调用旋转动画,都会让layer新起一次动画,上述代码会造成视图旋转角度越来越小,直至停止旋转(实际上还在旋转,只是动画效果叠加到我们肉眼看着动画是停止)所以需要我们再做点改进。

    // 在触发动画的action里面写入以下代码:
    // ⭐️根据键值开启或停止旋转动画防止动画叠加,判断demoView的图层是否已经有动画
    // 如果有就删除动画,如果没有就创建动画
     CAAnimation * animation = [demoView.layer animationForKey:@"viewRotationAnimate"];
     if (animation) {
         // 停止键值名为 @"viewRotationAnimate" 的动画
         [demoView.layer removeAnimationForKey:@"viewRotationAnimate"];
     }else{
         // 新起动画
         [self viewRotationAnimate];
     }

效果如下:
iOS动画开发_第3张图片
因为gif图片本来就卡顿,结果显示的更卡顿。。。

  • 创建一个缩放动画:
    //   ①.实例化动画对象
    CABasicAnimation * animate = [CABasicAnimation animationWithKeyPath:@"transform.scale"];// 缩放    
    
    //   ②.设置动画属性
    //   ②.1设置动画时长
    animate.duration = 0.6f;
    //   ②.2开始数值fromeValue  当前大小
    [animate setFromValue:@1.0];
    //   ②.3结束数值toValue     缩放大小
    [animate setToValue:@2.0];
    //   ②.4设置重复次数
    animate.repeatCount = 3;
    //   ②.5设置填充模式
    animate.fillMode = kCAFillModeForwards;
    animate.removedOnCompletion = NO;
    //   ②.6速度效果控制函数
    [animate setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
    
    //   ③.将动画添加到图层
    [demoView.layer addAnimation:animate forKey:nil];

效果如下:
iOS动画开发_第4张图片

在以上代码②.6中,我们看到了一个新的属性设置CAMediaTimingFunction,来解释一下:

kCAMediaTimingFunctionLinear选项创建了一个线性的计时函数,同样也是CAAnimation的timingFunction属性为空时候的默认函数。线性步调对于那些立即加速并且保持匀速到达终点的场景会有意义(例如射出枪膛的子弹),但是默认来说它看起来很奇怪,因为对大多数的动画来说确实很少用到。

kCAMediaTimingFunctionEaseIn常量创建了一个慢慢加速然后突然停止的方法。对于之前提到的自由落体的例子来说很适合,或者比如对准一个目标的导弹的发射。

kCAMediaTimingFunctionEaseOut则恰恰相反,它以一个全速开始,然后慢慢减速停止。它有一个削弱的效果,应用的场景比如一扇门慢慢地关上,而不是砰地一声。

kCAMediaTimingFunctionEaseInEaseOut创建了一个慢慢加速然后再慢慢减速的过程。这是现实世界大多数物体移动的方式,也是大多数动画来说最好的选择。如果只可以用一种缓冲函数的话,那就必须是它了。那么你会疑惑为什么这不是默认的选择,实际上当使用UIView的动画方法时,他的确是默认的,但当创建CAAnimation的时候,就需要手动设置它了。

kCAMediaTimingFunctionDefault,它和kCAMediaTimingFunctionEaseInEaseOut很类似,但是加速和减速的过程都稍微有些慢。

[该片段摘取自http://www.cocoachina.com/ios/20150105/10829.html]

来个通俗易懂的:

属性 实现效果
kCAMediaTimingFunctionLinear 匀速的线性计时函数
kCAMediaTimingFunctionEaseIn 缓慢加速,然后突然停止
kCAMediaTimingFunctionEaseOut 全速开始,慢慢减速
kCAMediaTimingFunctionEaseInEaseOut 慢慢加速再慢慢减速
kCAMediaTimingFunctionDefault 也是慢慢加速再慢慢减速,但是它加速减速速度略慢
  • 创建一个淡入淡出动画:
    //   ①.实例化动画对象
    CABasicAnimation * animate = [CABasicAnimation animationWithKeyPath:@"opacity"];// 淡入淡出
    
    //   ②.设置动画属性
    //   ②.1设置动画时长
    animate.duration = 0.5f;
    //   ②.2开始数值fromeValue  当前透明度
    [animate setFromValue:@1.0];
    //   ②.3结束数值toValue     目标透明度
    [animate setToValue:@0.2];
    //   ②.4设置重复次数
    animate.repeatCount = 3;
    //   ②.6设置自动翻转动画
    animate.autoreverses = YES;

    //   ③.将动画添加到图层
    [demoView.layer addAnimation:animate forKey:nil];

淡入淡出没有什么技术要点,效果如图:
iOS动画开发_第5张图片

很多人说你总是重复的写这些步骤干嘛?我只想说,这些都是基础创建步骤,每一次都能按照步骤来敲代码,可以让自己对这些代码有更深的映像。

上面的字体有点大了,好了,我们接着往下

2.关键帧动画 CAKeyframeAnimation

关键帧动画需要我们知道的点:
①CABasicAnimation 只能从 fromValue 变化到 toValue,而关键帧动画会使用一个数组保存这些数值
②values:数组对象,元素为关键帧。动画对象会在指定时间内,依次显示values数组中的每一个关键帧
③path:可以设置一个CGPathRef、CGMutablePathRef,让图层按照路径轨迹移动。path支队CALayer的anchorPoint和position起作用。
⭐️⭐️⭐️如果设置了path那么values会被忽略
④keyTimes:⭐️可以为对应的关键帧指定对应的时间点,其取值范围是0到1.0,keyTimes中的每一个时间值都对应values中的每一帧。u如果没有设置keyTimes,各个关键帧的时间是平分的

好了,开始铺代码:

  • 创建一个指定点平移动画:
    // ①.初始化动画属性
    CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    
    // ②.设置动画属性
    // ②.1 NSArray ,数组不能直接存入点,需要NSValue转换
    // 初始点
    NSValue *p1 = [NSValue valueWithCGPoint:CGPointMake(50, 50)];
    // 中间点(折返点)
    NSValue *p2 = [NSValue valueWithCGPoint:CGPointMake(arc4random() % 屏幕宽度, arc4random() & 屏幕高度)];
    // 目标点
    NSValue *p3 = [NSValue valueWithCGPoint:CGPointMake(60, 500)];
    
    NSArray * array = [NSArray arrayWithObjects:p1,p2,p3, nil];
    // ②.2设置关键帧动画的位置点  (点可以多个,不只是3个)
    [animation setValues:array];
    // ②.3设置时长
    animation.duration = 0.5f;
    // ②.4设置动画填充方法
    [animation setFillMode:kCAFillModeForwards];
    animation.removedOnCompletion = NO;
        
    // ③.将动画添加到图层
    [demoView.layer addAnimation:animation forKey:nil];

这里我们加入一个小小的需求,我们都知道CALayer的动画,改变的只是layer的位置,原View的位置是不会变的,我们现在需要在动画结束的时候demoView的位置也能到达指定位置。

把这段代码放在②.4后面。

    // 解决目标点设定视图位置的问题
    animation.delegate = self;
    // 通过键值存入本次动画的名字,以及要传出的参数
    // 在动画结束的代理中修改位置
    [animation setValue:p3 forKey:@"location"];
    [animation setValue:@"positionchange" forKey:@"animationType"];

然后我们去CAAnimationDelegate的代理里面完成:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    // 通过键值得传递方式,在动画完成之后,设定视图的目标位置
    NSString * type = [anim valueForKey:@"animationType"];
    if ([type isEqualToString:@"positionchange"]) {
        CGPoint location = [[anim valueForKey:@"location"]CGPointValue];
        demoView.center = location;
    }
}

最后效果如下:
iOS动画开发_第6张图片

  • 创建一个路径平移动画:
    // ①.初始化动画属性
    CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    
    // ②.设置动画属性 
    // ②.1创建路径
    CGMutablePathRef path = CGPathCreateMutable();
    // ②.2设置路径的属性
    CGPathAddEllipseInRect(path, NULL, CGRectMake(80, 80, 250, 250));// 画一个圆形路径
    // ②.3将路径设置给动画
    [animation setPath:path];
    // ②.4释放路径
    CGPathRelease(path);
    
    [animation setDuration:1.0f];
    
    // 修改动画的填充方法
    [animation setFillMode:kCAFillModeForwards];
    [animation setRemovedOnCompletion:NO];
    
    // ③.将动画添加到图层
    [demoView.layer addAnimation:animation forKey:nil];

在路径动画里面,需要自己去了解CGMutablePathRef,用它去创建路径,这是一个烧脑的东西。

效果图如下:
iOS动画开发_第7张图片

一般推荐UI框架自带的贝塞尔曲线,可以用以下代码替换CGMutablePathRef那一段,代码如下:

    // ②.1创建路径
    UIBezierPath * path = [UIBezierPath bezierPathWithRect:CGRectMake(75, 75, 200, 200)];
    // ②.2设置路径的属性
    
    // ②.3将路径设置给动画
    [animation setPath:path.CGPath];
  • 创建一个模仿删除App的晃动动画:
    // ①.初始化动画属性
    CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation"];// 旋转(旋转摇晃)
    
    // ②.设置动画属性 ⭐️思路:摇晃,从一个点,摇晃到另一个点
    // ②.1定义角度值
    // NSArray * array = @[角度数值]
    CGFloat angle = M_PI_4 / 9.0;// 每次抖动5度
    NSArray * array = @[@(angle),@(-angle),@(angle)];// 开始角度,经过角度,回复角度 相当于 左、右、左 然后重复
    // ②.2设置动画数组
    [animation setValues:array];
    
    // 修改动画的填充方法
    [animation setFillMode:kCAFillModeForwards];
    [animation setRemovedOnCompletion:NO];
    
    // 设置时长
    animation.duration = 0.2f;
    // 设置重复
    [animation setRepeatCount:MAXFLOAT];
    // 反转动画
    
    // ③.将动画添加到图层
    [demoView.layer addAnimation:animation forKey:nil];

效果图如下:
iOS动画开发_第8张图片

3.动画组 CAAnimationGroup

动画组,可以保存一组动画对象,将CAAnimationGroup对象加入层厚,组中所有动画对象可以同时并发运行

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

上代码,老套路,但是动画组的代码我有所删减,如果自己有兴趣可以补上:

    /// 1.创建动画对象
    CAAnimationGroup * group = [CAAnimationGroup animation];
    
    /// 2.定义一组动画
    // 透明度动画
    CABasicAnimation * alphaAnim = 透明度动画,反复,重复次数MAXFLOAT      
    // 旋转动画
    CABasicAnimation * rotationAnim = 旋转动画,反复,重复次数MAXFLOAT
    // 缩放动画
    CABasicAnimation * scaleAnim = 缩放动画,反复,重复次数MAXFLOAT

    // 定义关键帧动画S曲线
    CAKeyframeAnimation * pathAnim = S曲线需要两个路径点,使用贝塞尔曲线去画
    
    /// 3.设定动画组属性
    [group setDuration:4.0];
    
    /// 4.设定动画组
    NSArray * array = @[alphaAnim,rotationAnim,scaleAnim,pathAnim];
    [group setAnimations:array];
    
    /// 5.将动画组添加到图层
    [demoView.layer addAnimation:group forKey:nil];

实现效果如下:
iOS动画开发_第9张图片

4.转场动画 CATransition

CATransition 转场动画,能够为层提供移出屏幕和移入屏幕的动画效果
⭐️UINavigationController就是实现了CATransition动画

属性 实现效果
type 动画过渡类型
subtype 动画过渡方向
startProgress 动画起点
endProgress 动画终点

上代码:

    // 1.创建动画对象
    CATransition  *animate = [CATransition animation];
    
    // 2.设定属性
    animate.duration = 0.6 ;
    // 2.1设置动画的切换时间速度
    animate.timingFunction = [CAMediaTimingFunction functionWithName:@"easeInEaseOut"];
    // 2.2设置视图的切换方式
    animate.type = kCATransitionReveal;
    // 2.3设置视图切换的方向
    animate.subtype = kCATransitionFromRight ;

    // 3.将动画组添加到图层
    [demoView.layer addAnimation:animate forKey:@"transition"];

转场效果简单演示如图:
iOS动画开发_第10张图片
在转场的效果里面,有官方的效果,也有私有的Api(私有Api以NSString形式赋值):

属性 实现效果
@“cube” 立方体效果
@“suckEffect” 犹如一块布被抽走
@“oglFlip” 上下翻转效果
@“rippleEffect” 滴水效果
@“pageCurl” 向左翻页
@“pageUnCurl” 向下翻页
kCATransitionMoveIn 新的视图把旧的视图掩盖
kCATransitionPush 旧的视图移走,新的视图移进来
kCATransitionFade 逐渐消失,相当于调整透明度,除了这没有方向,其他的都有
kCATransitionReveal 旧的视图移走,显示出新的视图

然后在UINavigationController中,我们如何修改效果呢?话不多说,直接上:

    CATransition *animate = [CATransition animation];
    animate.duration = 0.5;
    animate.timingFunction = [CAMediaTimingFunction functionWithName:@"easeInEaseOut"];
    animate.type  = kCATransitionMoveIn;
    animate.subtype = kCATransitionFromTop;
    [self.navigationController.view.layer addAnimation:animate forKey:@"test"];
    UIViewController *targetVC = [[UIViewController alloc] init];
    [self.navigationController pushViewController:targetVC animated:NO];

华丽的分割线


好了,关于CoreAnimation就说这么多啦,想要实现非常牛逼的动画,一定要学好算法,使用路径来画出美丽的动画,CoreAnimation真的很强大,深入学习也能让自己很出彩,而我这本屌只能带你们到这一步了,希望大家都是为了自己的兴趣爱好和学习来敲代码,而不是做代码搬运工,加油哦!

另外还有两篇觉得也不错的文章

//转场动画

http://www.cocoachina.com/ios/20141226/10775.html

//核心动画

http://www.jianshu.com/p/d05d19f70bac

你可能感兴趣的:(iOS动画开发)