iOS常用动画内容以及原理

最近在做iOS界面转场的动画,写完转场入口后基本元素还是回归到我们常用的基本动画代码,有关动画的帖子网络上一搜一大把,而且介绍的都比较不错,本文还是不厌其烦的对基本常用到的动画做一个详细统一介绍和总结,主要记录下自己学习动画的点。

App开发中几乎都使用过动画,绚丽的动画效果也是iOS系统的一大亮点;使用动画可以让我们的App非常炫酷,同时更重要的是大大的提升了用户体验。那动画是什么呢?动画实质其实就是一张张连续的图片在一定的时间内连续展示出来的效果。打个比方说:把很多张慢动作的武打戏图片连续播放合成一段小视频。动画就是和这个视频是一样的效果!实质就是产生连续的假象来欺骗人的眼睛,从而达到动画的效果!

iOS开发中,常用的动画有UIView动画核心动画,很多书籍上有区分隐式动画显示动画以及其他动画的详细介绍,下文中会简单讲到,如需要详细介绍的可以去查找下相关资料。UIView动画核心动画一图是iOS提供的动画API框架结构,UIView动画:UIKit / AppKit;核心动画包括:CATransaction动画以及Core Animation等框架,Core Animation等框架位于UIKit的下一层,相比UIView的动画,它可以实现更复杂的动画效果。下面我们先来介绍下iOS中简单常用的UIView动画的内容及其使用。

UIView动画和核心动画

Tip:在模拟器上运行时,我们可以点击菜单栏Debug下的Slow Animation或者快捷键command+T,来放慢App中的动画效果并分析动画内容。

一、UIView类实现的常用动画方法

对于每一个UIView都有一个Layer属性,把这个Layer且称作为RootLayer,而不是UIViewRootLayer的叫做非RootLayer。我们对UIView的属性修改时时不会产生默认动画,而对非RootLayer属性直接修改会产生动画,这个默认动画的时间缺省值是0.25s,这个动画也称作隐式动画。UIView默认情况下禁止了RootLayer的隐式动画,但是在UIVIewanimation block方法中又重新启用了它们。(有关UIViewCALayer介绍稍后详说)

需要注意的是:使用UIView的动画方法,只能针对动画属性修改才能产生动画效果。UIView支持的动画属性有frameboundscentertransformalphabackgroundColorcontentStretch等。

第一种:使用UIView的 [beginAnimations … commitAniamtions] 模式来实现动画

先过一遍系统的API方法:

//开始动画
+ (void)beginAnimations:(NSString *)animationID context:(void *)context;

//提交动画
+ (void)commitAnimations;

//设置动画代理对象
+ (void)setAnimationDelegate:(id)delegate;

//当动画即将开始时执行指定的SEL方法《必须要先设置动画代理》
+ (void)setAnimationWillStartSelector:(SEL)selector

//当动画结束时执行指定的SEL方法《必须要先设置动画代理》
+ (void)setAnimationDidStopSelector:(SEL)selector;

//设置动画的持续时间,单位为:秒
+ (void)setAnimationDuration:(NSTimeInterval)duration;

//设置动画延迟执行,单位为:秒
+ (void)setAnimationDelay:(NSTimeInterval)delay;

//动画的开始时间,默认为:立即
+ (void)setAnimationStartDate:(NSDate *)startDate;

//动画的速度曲线控制
+ (void)setAnimationCurve:(UIViewAnimationCurve)curve;

//动画的重复次数
+ (void)setAnimationRepeatCount:(float)repeatCount;

//如果设置为YES,代表动画每次重复执行的效果会跟上一次相反
+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses;

//设置视图view的过渡效果,transition指定过渡类型,cache设置YES代表使用视图缓存,性能较好
+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache;

//当设置的时候忽视所有属性的改变
+ (void)setAnimationsEnabled:(BOOL)enabled;

这种动画的使用方法比较简单,将动画代码写在begincommit之间就完成了动画的编写

[UIView beginAnimations:@"animation" context:nil]; //准备动画
// Code... 【在此处修改view的动画属性,需注意:有些属性不支持动画操作的】
[UIView commitAnimations]; //提交动画

我们举个简单的透明度渐变的例子:

//获取当前视图的上下文,在此处传入上下文可以在代理方法中使用到
CGContextRef context = UIGraphicsGetCurrentContext();  
//准备编写动画代码
[UIView beginAnimations:@"AlphaAnimation" context:context];
[UIView setAnimationDuration:0.5f];
[animationView setAlpha:0.0f];
[UIView commitAnimations];

第二种:使用UIView的block代码块调用动画

在iOS4的时候苹果大量的添加了Block API,之前的动画API也不例外。使用动画的Block API后,方便了动画嵌套使用,也让我们的代码更清晰,使用也更简单了!不多说了,下面来介绍下Block的内容。
UIViewblock动画方法中常见的参数介绍:

duration   : 动画时长
delay      : 决定了动画在延迟多久之后执行
options    : 用来决定动画的展示方式,参照下面的枚举参数说明
animations : 具体动画内容的代码
completion : 动画结束后执行的代码块

枚举参数说明:

enum {
    //这部分是基础属性的设置   
    UIViewAnimationOptionLayoutSubviews            = 1 <<  0, //设置子视图随父视图展示动画   
    UIViewAnimationOptionAllowUserInteraction      = 1 <<  1, //允许在动画执行时用户与其进行交互   
    UIViewAnimationOptionBeginFromCurrentState     = 1 <<  2, //允许在动画执行时执行新的动画   
    UIViewAnimationOptionRepeat                    = 1 <<  3, //设置动画循环执行   
    UIViewAnimationOptionAutoreverse               = 1 <<  4, //设置动画反向执行,必须和重复执行一起使用   
    UIViewAnimationOptionOverrideInheritedDuration = 1 <<  5, //强制动画使用内层动画的时间值
    UIViewAnimationOptionOverrideInheritedCurve    = 1 <<  6, //强制动画使用内层动画曲线值   
    UIViewAnimationOptionAllowAnimatedContent      = 1 <<  7, //设置动画视图实时刷新
    UIViewAnimationOptionShowHideTransitionViews   = 1 <<  8, //设置视图切换时隐藏,而不是移除
    UIViewAnimationOptionOverrideInheritedOptions  = 1 <<  9, //   
    //这部分属性设置动画播放的线性效果
    UIViewAnimationOptionCurveEaseInOut            = 0 << 16, //淡入淡出 首末减速   
    UIViewAnimationOptionCurveEaseIn               = 1 << 16, //淡入 初始减速   
    UIViewAnimationOptionCurveEaseOut              = 2 << 16, //淡出 末尾减速   
    UIViewAnimationOptionCurveLinear               = 3 << 16, //线性 匀速执行
    //这部分设置UIView切换效果
    UIViewAnimationOptionTransitionNone            = 0 << 20,
    UIViewAnimationOptionTransitionFlipFromLeft    = 1 << 20, //从左边切入   
    UIViewAnimationOptionTransitionFlipFromRight   = 2 << 20, //从右边切入   
    UIViewAnimationOptionTransitionCurlUp          = 3 << 20, //从上面立体进入   
    UIViewAnimationOptionTransitionCurlDown        = 4 << 20, //从下面立体进入   
    UIViewAnimationOptionTransitionCrossDissolve   = 5 << 20, //溶解效果
    UIViewAnimationOptionTransitionFlipFromTop     = 6 << 20, //从上面切入   
    UIViewAnimationOptionTransitionFlipFromBottom  = 7 << 20, //从下面切入
};
注意:此类型可以使用“|”进行多项一起使用

①、UIView实现的动画最常用普通的方法
系统API的说明和功能介绍:

//一般的动画
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void(^)(void))animations completion:(void(^)(BOOL finished))completion;
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void(^)(void))animations completion:(void(^)(BOOL finished))completion;
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void(^)(void))animations;

常用的动画使用也比较简单,把之前写在begincommit之间的代码移动到block中就完成动画编写,如:

[UIView animateWithDuration:4.0 // 动画时长
                      delay:2.0 // 动画延迟
                    options:UIViewAnimationOptionCurveEaseIn // 动画过渡效果
                 animations:^{ //code... 动画内容 }
                 completion:^(BOOL finished) { //code... 动画完成后执行 }];

同样的,还以透明度渐变为例子:

[UIView animateWithDuration:0.5 // 动画时长
                      delay:0.0 // 动画延迟
                    options:UIViewAnimationOptionCurveEaseIn // 动画过渡效果
                 animations:^{ [animationView setAlpha:0.0f]; }
                 completion:^(BOOL finished) { //code... 动画完成后执行 }];

②、UIView的转场动画类方法
系统API的说明和功能介绍:

//转场动画
+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void(^)(void))animations completion:(void(^)(BOOL finished))completion;
+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void(^)(BOOL finished))completion;
/*第二个方法调用完毕后,还需要处理View的层次关系,添加toView到父视图并移除fromView*/

转场动画换个卡牌翻转的例子,示例代码如:

imageView.image = [UIImage imageNamed:@"背景图片"];
[UIView transitionWithView:imageView 
                  duration:0.5 
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{ imageView.image = [UIImage imageNamed:@"内容图片"]; }
                completion:^(BOOL finished) { //code... 动画完成后执行 }];

③、UIView的弹簧动画类方法(iOS 7后添加的API)
这个方法是iOS7之后的一个新方法,通过这个方法,可以方便的制作出弹簧振动效果的动画,这个方法的核心是两个阻尼参数。
弹簧动画API的说明和功能介绍:

//弹簧的弹性动画(iOS7之后新加的方法)
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void(^)(void))animations completion:(void(^)(BOOL finished))completion;

动画方法中的新参数:

usingSpringWithDamping : 取值范围[0.0~1.0],数值越小振动幅度越大 
initialSpringVelocity  : 初始速度,数值越大开始移动越快

基本写法及示例代码:

[UIView animateWithDuration:4.0 // 动画时长                      
                      delay:0.0 // 动画延迟     
     usingSpringWithDamping:1.0 // 类似弹簧振动效果 0~1      
      initialSpringVelocity:5.0 // 初始速度                    
                    options:UIViewAnimationOptionCurveEaseInOut // 动画过渡效果
                 animations:^{ // code... } 
                 completion:^(BOOL finished) { // code... }];

④、UIView的关键帧动画类方法(iOS 7后添加的API)
关键帧动画是指:给出整个动画过程中的几个关键帧画面信息,关键帧之间的过渡帧都由计算机自动生成。
这个方法也是iOS7之后的一个新方法,通过这个方法可以不需要去使用到核心动画(Core Animation)就能创建关键帧的更多更复杂的动画效果。
关键帧动画API的说明和功能介绍:

//创建关键帧方法
+ (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void(^)(void))animations completion:(void(^)(BOOL finished))completion;

//添加关键帧
+ (void)addKeyframeWithRelativeStartTime:(double)frameStartTime relativeDuration:(double)frameDuration animations:(void(^)(void))animations;

以简单的单次抖动作为示例:(这写复杂了仅作参考)

    __weak UIView *weakView = self.animationView;
    void(^animations)(void) = ^{
        //向右旋转
        void(^leftCallblock)(void) = ^{
            weakView.transform = CGAffineTransformRotate(weakView.transform,M_PI/9);
        };
        [UIView addKeyframeWithRelativeStartTime:0.0   //相对于6秒所开始的时间(第0秒开始动画)
                                relativeDuration:1/4.0 //相对于6秒动画的持续时间(动画持续2秒)
                                      animations:leftCallblock];
        
        //复原
        void(^resetblock)(void) = ^{
            weakView.transform = CGAffineTransformIdentity;
        };
        [UIView addKeyframeWithRelativeStartTime:1/4.0   //相对于6秒所开始的时间(第0秒开始动画)
                                relativeDuration:1/4.0 //相对于6秒动画的持续时间(动画持续2秒)
                                      animations:resetblock];
        
        //向左旋转
        void(^rightCallblock)(void) = ^{
            weakView.transform = CGAffineTransformRotate(weakView.transform,-M_PI/9);
        };
        [UIView addKeyframeWithRelativeStartTime:2/4.0   //相对于6秒所开始的时间(第0秒开始动画)
                                relativeDuration:1/4.0 //相对于6秒动画的持续时间(动画持续2秒)
                                      animations:rightCallblock];
        
        //复原
        [UIView addKeyframeWithRelativeStartTime:3/4.0   //相对于6秒所开始的时间(第0秒开始动画)
                                relativeDuration:1/4.0 //相对于6秒动画的持续时间(动画持续2秒)
                                      animations:resetblock];
    };
    [UIView animateKeyframesWithDuration:0.5f
                                   delay:0.0f
                                 options:UIViewKeyframeAnimationOptionCalculationModeLinear
                              animations:animations
                              completion:NULL];

关键帧新加的枚举参数:

enum {
    UIViewKeyframeAnimationOptionCalculationModeLinear    = 0 << 10, // default 
    UIViewKeyframeAnimationOptionCalculationModeDiscrete  = 1 << 10,
    UIViewKeyframeAnimationOptionCalculationModePaced     = 2 << 10,
    UIViewKeyframeAnimationOptionCalculationModeCubic     = 3 << 10,
    UIViewKeyframeAnimationOptionCalculationModeCubicPaced = 4 << 10
};

这里的示例中使用到了transform属性,额外再记录下transform属性
transform提供的API来说主要体现在缩放、位移和旋转三个方面,transform的运算是按照矩阵(3x3矩阵)进行运算的,这样会减少运算量,提高效率。
关于transform和矩阵的详细内容请参考:iOS开发笔记之transform

//用来连接两个变换效果并返回。返回的t = t1 * t2
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);

//矩阵初始值。[ 1 0 0 1 0 0 ]
CGAffineTransformIdentity;

//自定义矩阵变换,需要掌握矩阵变换的知识才知道怎么用。参照上面推荐的原理链接
CGAffineTransformMake(CGFloat a, CGFloat b, CGFloat c, CGFloat d, CGFloat tx, CGFloat ty);

/*
    旋转视图
        1.当参数x>0 && x<=M_PI时,为顺时针
        2.当参数x>-M_PI && x<0时,为逆时针
        3.若参数x<M_PI || x>2.0*M_PI时,则旋转方向等同于x%2的旋转方向
    总结:旋转方向就是向最短路径方向旋转
*/
CGAffineTransformMakeRotation(CGFloat angle);
CGAffineTransformRotate(CGAffineTransform t, CGFloat angle);
​
//缩放视图。等同于CGAffineTransformScale(self.transform, sx, sy)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy);
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy);

//平移视图。等同于CGAffineTransformTranslate(self.transform, tx, ty)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty);
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty);

二、核心动画(Core Animation框架)

Core Animation框架是基于OpenGLCore Graphics图像处理框架的一个跨平台的动画框架。在真实的开发中,一般还是主要使用UIView封装的动画,而很少使用Core Animation的动画;使用UIViewCALayer都能实现动画效果,但是当一般的UIView动画不能满足我们的需求时,就需要使用核心动画来实现。核心动画是直接作用在CALayer上的动画,而非UIView对象;所以介绍核心动画前,我们先了解下UIViewCALayer之间的联系以及区别。

关联引申⭐️⭐️⭐️⭐️⭐️
CALayer是属于Core Animation部分的内容,包含在QuartzCore框架中,这是一个跨平台的框架,既可以用在iOS中又可以用在Mac OS X中;实际上CALayer只是捕获App所提供的内容,并缓存成bitmap(位图),当任何与CALayer关联的属性值发生变化时,Core Animation就会将新的bitmap(位图)传给绘图硬件,并根据新的bitmap(位图)更新显示。
UIViewiOS系统中界面元素的基础,所有的界面元素都是继承自UIView类,它真正的绘图部分,是由一个CALayer类来管理。每个UIView默认就包含一个CALayer属性,UIView就像是一个CALayer的管理器,在View显示的时候,UIView做为CALayerCALayerDelegateUIView的显示内容由内部的CALayer来显示。访问UIView的与绘图和跟坐标有关的属性,例如framebounds等,实际上内部都是在访问它所包含的CALayer的相关属性。有人说UIView就像一个画板,而layer就像画布,一个画板上可以有很多块画布,但是画布不能有画板。

CALayerUIView类似都拥有自己的树形结构,UIView包含很多subView,而CALayer是可以包含subLayer的,因此形成了图层树,CALayer层是可以嵌套的,可以向它的layer上添加subLayer,来完成某些特殊内容的显示。

UIViewCALayer的主要区别
1、UIView是可以响应事件的,但是CALayer不能响应事件
2、UIView主要负责管理显示内容,而CALayer主要负责渲染和呈现。如果没有CALayer,我们是看不到内容的。
3、CALayer内部维护着三分layer tree,分别是presentLayer tree(动画树),modeLayer tree(模型树),render tree(渲染树),在做iOS动画的时候,我们修改动画的属性,在动画的其实是CALayerpresent Layer的属性值,而最终展示在界面上的其实是提供UIViewmodelLayer

CALayer核心动画与UIView动画的区别:
UIView封装的动画执行完毕之后不会反弹,CALayer核心动画则会;另外UIView的动画期间可以处理用户事件,CALayer核心动画则不能。例如:如果是通过CALayer核心动画改变layer的位置状态,表面上看虽然已经改变了,但是实际上它的位置是没有改变的。

核心动画官方文档地址:Core Animation Guide

核心动画是iOSOS X上的图形渲染和动画基础设施,用于为应用程序的视图和其他视觉元素设置动画。使用核心动画,为您绘制每幅画面所需的大部分工作。所有你需要做的是配置一些动画参数(如开始和结束点),并告诉核心动画开始。核心动画完成其余的工作,将大部分实际的绘图工作交给板载图形硬件,以加速渲染。这种自动图形加速可以实现高帧速率和平滑动画,而不会对CPU造成负担,并减慢应用程序的速度。

核心动画的继承结构

CAAnimation <CAMediaTiming> {
    CATransition
    CAAnimationGroup
    CAPropertyAnimation {
        CABasicAnimation {
            CASpringAnimation  (iOS 9才开始支持)
        }
        CAKeyframeAnimation
    }
}

核心动画的继承关系与常用属性如下图


核心动画的继承关系与常用属性

CAAnimation是所有动画对象的父类,它遵守CAMediaTiming协议,负责控制动画的时间、速度和时间曲线等等,是一个抽象类,不能直接使用。CAPropertyAnimationCAAnimation的子类,它支持动画地显示图层的KeyPath,也不能直接使用。
iOS9.0之后新增CASpringAnimation类,它实现弹簧效果的动画是CABasicAnimation的子类
综上,核心动画类中可以直接使用的类有:CATransitionCABasicAnimationCASpringAnimationCAKeyframeAnimationCAAnimationGroup五个类。
①、CAAnimation的属性介绍:注意CAAnimation不能直接使用

//动画开始结束的回调代理
@property (nullable, assgin) id delegate;
//动画完成后是否移除动画,默认为YES
@property (getter=isRemovedOnCompletion) BOOL removedOnCompletion;
//动画的动作规则,包含匀速、慢进快出、快进慢出、慢进慢出<中间加速>、默认
@property (nullable, strong) CAMediaTimingFunction *timingFunction;

+ (id)animation;    //类方法创建动画对象

注意:
timingFunction 可通过系统提供的API自定义喜欢的曲线函数来处理动画
动画的removedOnCompletion属性需要配合fillMode来使用。默认为YES,fillMode不可用

CAAnimation的协议属性介绍:

//开始时间,例如:CACurrentMediaTime() + x 其中x为延迟时间
@property CFTimeInterval beginTime;
//动画执行时间,与speed有关系,默认为1.0,如果speed=2.0,那么执行时间为:durantion*(1.0/2.0)
@property CFTimeInterval duration;
//动画执行速度
@property float speed;
//动画的时间延迟,默认为0
@property CFTimeInterval timeOffset;
//重复执行次数
@property float repeatCount;
//重复执行时间,此属性优先级大于repeatCount
@property CFTimeInterval repeatDuration;
//是否自动翻转动画,默认为NO,如果为YES,那么整个动画效果为A->B->A
@property BOOL autoreverses;
/*动画的填充方式,
动画结束后回到准备状态(kCAFillModeForwards)、
动画结束后保持最后状态(kCAFillModeBackwards)、
动画结束后回到准备状态并保持最后状态(kCAFillModelBoth)、
执行完成移除动画(kCAFillModeRemoved)
*/
@property (copy) NSString *fillMode;

动画代理对象协议

//动画开始时回调此方法
- (void)animationDidStart:(CAAnimation *)animation;

//动画结束时回调此方法
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag;

②、CATransition转场动画的使用
CATransition的属性介绍:

@property (copy) NSString *type;  //转场类型
@property (nullable, copy) NSString *subtype;  //转场方向
@property float startProgress;  //开始进度,默认0.0,如果为0.4则直接从0.4开始动画
@property float endProgress;  //结束进度,默认1.0,如果为0.6则直接到0.6结束动画
@property (nullable, strong) id filter;  //开始进度

转场类型有私有API非私有API两种

可供type选择的值有以下内容(转场类型)
{
    //公开API转场种类
    kCATransitionFade      渐变
    kCATransitionMoveIn    覆盖
    kCATransitionPush      推出
    kCATransitionReveal    揭开
    //私有API转场种类
    @"cube"                      立方体翻转效果
    @"oglFlip"                   翻转效果
    @"suckEffect"                收缩效果,动画方向不可控
    @"rippleEffect"              水滴波纹效果,动画方向不可控
    @"pageCurl"                  向上翻页效果
    @"pageUnCurl"                向下翻页效果
    @"cameralIrisHollowOpen"     摄像头打开效果
    @"cameralIrisHollowClose"    摄像头关闭效果
};

可供subtype选择的值有以下内容(转场方向)
{
    kCATransitionFromRight     从右开始
    kCATransitionFromLeft      从左开始
    kCATransitionFromTop       从上开始
    kCATransitionFromBottom    从下开始
};

转场动画的基本写法及示例代码:(以淡入淡出的动画效果为例)

CATransition *animation = [CATransition animation];
animation.type = kCATransitionFade;
animation.subType = kCATransitionFromLeft;
animation.duration = 0.5f;
self.imageView.image = [UIImage imageNamed:@"动画后的图片"];
[self.imageView.layer addAnimation:animation forKey:@"transition"];

③、CAPropertyAnimation的子类CABasicAnimation的使用
CAPropertyAnimation的子类初始化方法

//通过key创建一个CAPropertyAnimation对象
+ (id)animationWithKeyPath:(NSString *)path;

注意:path可以设置动画的属性列表
CATransform3D {
    旋转
    rotation
    transform.rotation.x    
    transform.rotation.y
    transform.rotation.z
    缩放
    scale
    transform.scale.x        
    transform.scale.y
    transform.scale.z
    平移
    translation
    transform.translation.x    
    transform.translation.y
    transform.translation.z
}
CGPoint {
    position
    position.x
    position.y
}
CGRect {
    bounds
    bounds.size
    bounds.size.width
    bounds.size.height
    
    bounds.origin
    bounds.origin.x
    bounds.origin.y
}
property {
    opacity
    backgroundColor
    cornerRadius
    boarderWidth
    contents
    
    SHadow {
        shadowColor
        shadowOffset
        shadowOpacity
        shadowRadius
    }
}
注意:CAPropertyAnimation是抽象类,不能直接使用,只能使用子类

CABasicAnimation属性介绍:

@property (nullable, strong) id fromValue;  //开始值
@property (nullable, strong) id toValue;    //结束值
@property (nullable, strong) id byValue;    //偏移值

CABasicAnimation属性之间的规则:

1、fromValue和toValue不为空,动画的效果会从fromValue的值变化到toValue
2、fromValue和byValue不为空,动画的效果将会从fromValue变化到fromValue+byValue
3、toValue和byValue都不为空,动画的效果将会从toValue-byValue变化到toValue
4、仅fromValue的值不为空,动画的效果将会从fromValue的值变化到当前的状态
5、仅toValue的值不为空,动画的效果将会从当前状态的值变化到toValue的值
6、仅byValue的值不为空,动画的效果将会从当前的值变化到(当前状态的值+byValue)的值

CABasicAnimation动画的基本写法及示例代码:(以淡入淡出的动画效果为例)

CABasicAnimation *animation = nil;
animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
[animation setFromValue:@1.0];
[animation setToValue:@0.1];
[animation setDelegate:self];
[animation setDuration:0.25];
[animation setRemovedOnCompletion:NO];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[animation setAutoreverses:YES];
[animation setFillMode:kCAFillModeBoth];
[animationView.layer addAnimation:animation forKey:@"animation"];

④、CABasicAnimation的子类CASpringAnimation的使用
CASpringAnimation属性介绍:

@property CGFloat mass;             //质量,振幅与质量成反比
@property CGFloat stiffness;        //刚度系数,值越大,形变产生的力越大
@property CGFloat damping;          //阻尼系数,阻止弹簧伸缩的系数,值越大,停止越快
@property CGFloat initialVelocity;  //初始速率,速率为正数时速度与运动方向一致,为负数时,速度与运动方向相反
@property (readonly) CFTimeInterval settlingDuration;    //结算时间,只读。返回弹簧动画到停止时的估算时间

CASpringAnimation动画的基本写法及示例代码:

CASpringAnimation *spring = [CASpringAnimation animationWithKeyPath:@"position.y"];
spring.damping = 5;
spring.stiffness = 100;
spring.mass = 1;
spring.initialVelocity = 0;
spring.duration = spring.settlingDuration;
spring.formValue = @(animationView.cemter.y);
spring.toValue = @(animationView.center.y+(button.selected?100:-100));
spring.fillMode = kCAFillModeForwards;
[animationView.layer addAnimation:spring forKey:@"spring"];

⑤、CAPropertyAnimation的子类CAKeyframeAnimation的使用
CAKeyframeAnimation属性介绍:

//关键帧值数组,一组变化值
@property (nullable, copy) NSArray *values;
//关键帧帧路径,优先级比values高
@property (nullable) CGPathRef path;
//每一帧对应的时间,可以用来控制速度,它和每一帧对应,取值范围:[0.0~1.0],不设置则默认相等
@property (nullable, copy) NSArray *keyTimes;
//每一帧对应的时间曲线函数,也就是每一帧的运动节奏
@property (nullable, copy) NSArray *timingFunctions;
//动画的计算模式
@property (copy) NSString *calculationMode;
//动画的张力
@property (nullable, copy) NSArray *tensionValues;
//动画的连续性值
@property (nullable, copy) NSArray *continuityValues;
//动画的偏斜率
@property (nullable, copy) NSArray *biasValues;
//动画沿路径旋转方式,默认为nil
@property (nullable, copy) NSString *rotationMode;

//动画的计算模式有
enum {
  kCAAnimationLinear      //默认值,关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算;
  kCAAnimationDiscrete    //离散的,也就是没有补间动画
  kCAAnimationPaced       //平均
  kCAAnimationCubic       //对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算
  kCAAnimationCubicPaced  //在kCAAnimationCubic的基础上使得动画运行变得均匀
};
注意:使用kCAAnimationPaced和kCAAnimationCubic以及kCAAnimationCubicPaced时,keyTimes跟timeFunctions失效

//动画沿路径旋转方式
enum {
  kCAAnimationRotateAuto         //自动旋转
  kCAAnimationRotateAutoReverse  //自动翻转
};

CAKeyframeAnimation动画的基本写法及示例代码:

UIBezierPath *path = nil;
CAKeyframeAnimation *animation = nil;
path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 200, 100)];
animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
[animation setPath:path.CGPath];
[animation setDuration:0.5];
[animation setRemovedOnCompletion:NO];
[animation setFillMode:kCAFillModeBoth];
[animationView.layer addAnimation:animation forKey:nil];

⑥、CAAnimationGroup的使用
CAAnimationGroup属性介绍:

//数组中存放CAAnimation的动画对象
@property (nullable, copy) NSArray *animations;

CAAnimationGroup动画的基本写法及示例代码:

CGMutablePathRef path = CGPathCreateMutable();    
CGPathAddEllipseInRect(path, NULL, CGRectMake(0, 0, 320, 320));
CAKeyframeAnimation *position = [CAKeyframeAnimation animationWithKeyPath:@"position"];
[position setPath:path];
[position setDuration:3];
[position setRemovedOnCompletion:NO];
[position setFillMode:kCAFillModeForwards]; 
CGPathRelease(path);

CABasicAnimation *alpha = [CABasicAnimation animationWithKeyPath:@"opacity"];
[alpha setDuration:1.0];
[alpha setRepeatCount:3];
[alpha setAutoreverses:YES];
[alpha setFromValue:@1.0];
[alpha setDelegate:self];
[animation setToValue:@0.1];

CAAnimationGroup *group = [CAAnimationGroup animation];
[group setDuration:3.0];
[group setAnimations:@[position,alpha]];
[animationView.layer addAnimation:group forKey:nil];

三、核心动画和UIView动画的对比

核心动画UIView动画的对比:UIView动画可以看成是对核心动画的封装,和UIView动画不同的是,通过核心动画改变layer的状态(比如position),动画执行完毕后实际上是没有改变的(表面上看起来已改变)。
view加上动画,本质上是对其layer进行操作,layer包含了各种支持动画的属性,动画则包含了属性变化的值、变化的速度、变化的时间等等,两者结合产生动画的过程。
总体来说核心动画的优点有:
  - 性能强大,使用硬件加速,可以同时向多个图层添加不同的动画效果
  - 接口易用,只需要少量的代码就可以实现复杂的动画效果
  - 运行在后台线程中,在动画过程中响应交互事件(UIView动画默认动画过程中不响应交互事件)

四、CATransaction的使用

CATransaction顾名思义“事务”,创建动画事务的目的是为了操作的原子性,它的目的就是能保证多个“动画属性”的同时生效。它也分为隐式和显式,在RunLoop中修改“动画属性”,如果当前没有创建CATransaction,系统会自动创建一个CATransaction,并在当前线程的下一个RunLoopcommit这个CATransaction,这种动画方式称为隐式动画。使用CATransaction类中的[CATransaction begin][CATransaction commit]等相关方法创建的动画称为显式动画。显式事务中可以定义事务中所有动画的运行时间和时间函数,此外,还有这个方法+ (void)setDisableActions:(BOOL)flag能显式的关闭这个事务中的action查询操作,关闭了查询也就是关闭了动画效果,属性值的变化就会立即生效,而没有动画效果了。

关联引申⭐️⭐️⭐️⭐️⭐️
隐式动画的原理是:当我们直接对可动画属性赋值的时候,由于有隐式动画存在的可能,CALayer首先会判断此时有没有隐式动画被触发。它会让它的delegate(没错CALayer拥有一个属性叫做delegate)调用actionForLayer:forKey:来获取一个返回值,这个返回值在声明的时候是一个id对象,当然在运行时它可能是任何对象。这时CALayer拿到返回值,将进行判断:如果返回的对象是一个nil,则进行默认的隐式动画;如果返回的对象是一个[NSNull null] ,则CALayer不会做任何动画;如果是一个正确的实现了CAAction协议的对象,则CALayer用这个对象来生成一个CAAnimation,并加到自己身上进行动画。

以重写的CALayer的position属性的setter方法作例子:

- (void)setPosition:(CGPoint)position
{
    if ([self.delegate respondsToSelector:@selector(actionForLayer:forKey:)]) {
        id obj = [self.delegate actionForLayer:self forKey:@"position"];
        if (!obj) {
            // 隐式动画
        } else if ([obj isKindOfClass:[NSNull class]]) {
            // 直接重绘(无动画)
        } else {
            // 使用obj生成CAAnimation
            CAAnimation * animation;
            [self addAnimation:animation forKey:nil];
        }
    }
}

CATransaction属性介绍:

//在当前线程开启一个新的事务
+ (void)begin;

//提交当前事务期间所做的全部修改,如果不存在事务则会引发异常
+ (void)commit;

//提交一个现存的隐士事务,将推迟提交知道实际提交嵌套的显示事务已经完成
+ (void)flush;

//加锁,保证线程安全
+ (void)lock;
+ (void)unlock;

//获取和设定事务持续时间,默认为:0.25
+ (CFTimeInterval)animationDuration;
+ (void)setAnimationDuration:(CFTimeInterval)dur;

//暂时禁用图层的行为
+ (BOOL)disableActions;
+ (void)setDisableActions:(BOOL)flag;

CATransaction动画的基本写法及示例代码:

[CATransaction begin];  
[CATransaction setDisableActions:YES];  //关闭动画  
[CATransaction setValue:@(0.5f) forKey:kCATransactionAnimationDuration];  //动画时间  
animationLayer.position = CGPointMake(100, 100);  
[CATransaction commit];

注意:事务还支持嵌套,当嵌套的时候,只有最外层的事务commit了之后,整个动画才会开始。

五、UIImageView的动画

UIImageView的动画是可以让一系列的图片在特定的时间内按照顺序显示。
UIImageView类中有关动画的属性和方法介绍:

//动画普通状态下的图片数组
@property (nullable, nonatomic, copy) NSArray<UIImage *> *animationImages;
//动画高亮状态下的图片数组
@property (nullable, nonatomic, copy) NSArray<UIImage *> *highlightedAnimationImages;
//动画持续时间
@property (nonatomic) NSTimeInterval animationDuration;       
//动画重复次数
@property (nonatomic) NSInteger      animationRepeatCount;

- (void)startAnimating;    //开始动画
- (void)stopAnimating;     //结束动画
- (BOOL)isAnimating;       //是否在动画中

UIImageView的使用方法很简单按照要求传值即可,示例代码:

NSArray *imagesArray = @[[UIImage imageNamed:@"image1.png"],
                         [UIImage imageNamed:@"image2.png"],
                         [UIImage imageNamed:@"image3.png"]];
UIImageView *imageView = [UIImageView alloc] init];
[imageView setFrame:CGRectMake(0, 0, 100, 100)];
imageView.animationImages = imagesArray;  //将序列帧数组赋给animationImages属性
imageView.animationDuration = 0.25;  //设置动画时间
imageView.animationRepeatCount = 0;  //设置动画次数 0 表示无限
[self.view addSubview:imageView];
[imageView startAnimating];  //开始播放动画

值得注意的地方是:图片较少的情况,这种方法最简单实用;但如果图片很多的话,这种方式可能会导致程序出现问题。
UIImageView动画的其他缺陷是:动画不能够实现暂停只能停止。如有暂停的需求时,用animationImages这种方式实现已经不太满足要求。我们可以使用NSTimer去实现UIImageViewanimation的效果,用NSTimer每隔一个时间戳去设置一次image,具体代码如下:

//创建定时器定时更新图片内容
NSTimer *animatedTimer = [NSTimer scheduledTimerWithTimeInterval:0.04 
                                                          target:self 
                                                        selector:@selector(updateImageViewContentMethod) 
                                                        userInfo:nil 
                                                         repeats:YES];

//在定时器方法中更新图片内容                                                         
- (void)updateImageViewContentMethod
{
   animatedView.image = [UIImage imageNamed:[NSStringstringWithFormat:@"image%i.png",index]];
}

六、UIActivityIndicatorView的动画

UIActivityIndicatorView的是一个可以旋转的进度轮,一般用来当loading使用。
UIActivityIndicatorView的类介绍:

//进度轮的风格
@property(nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle;
//当动画停止时是否隐藏进度轮
@property(nonatomic) BOOL                         hidesWhenStopped;
//iOS5后添加的设置进度轮颜色的属性
@property (nullable, readwrite, nonatomic, strong) UIColor *color;
//初始化方法
- (id)initWithActivityIndicatorStyle:(UIActivityIndicatorViewStyle)style;
- (id)initWithFrame:(CGRect)frame;
//开始旋转
- (void)startAnimating;
//结束旋转
- (void)stopAnimating;
//是否正在旋转
- (BOOL)isAnimating;

UIActivityIndicatorView的初始化方法一般使用initWithActivityIndicatorStyle方法,UIActivityIndicatorViewStyle有三个枚举值可供选择:

enum {
    UIActivityIndicatorViewStyleWhiteLarge  //大型白色指示器    
    UIActivityIndicatorViewStyleWhite       //标准尺寸白色指示器    
    UIActivityIndicatorViewStyleGray        //灰色指示器,用于白色背景
};

基本使用方法及示例代码:

UIActivityIndicatorView *animationView = nil;
animationView = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
animationView.center = self.view.center;
[animationView setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleWhiteLarge];
[animationView setBackgroundColor:[UIColor lightGrayColor]];
[self.view addSubview:animationView];
[animationView startAnimating];

关联引申⭐️⭐️⭐️⭐️⭐️
关于另一种旋转轮:经常在网络请求时状态栏上也出现旋转轮,这个是怎么添加的。具体代码:

[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 6,633评论 4 25
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 3,843评论 5 10
  • 前言 本文只要描述了iOS中的Core Animation(核心动画:隐式动画、显示动画)、贝塞尔曲线、UIVie...
    GitHubPorter阅读 2,928评论 7 11
  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 2,201评论 1 22
  • 目录: 主要绘图框架介绍 CALayer 绘图 贝塞尔曲线-UIBezierPath CALayer子类 补充:i...
    Ryan___阅读 1,039评论 1 9