iOS 动画效果详解



转:http://geeklu.com/2012/09/animation-in-ios/

html


零.前言ios

这里没有太多的代码细节,只是探索iOS动画的基本概念,以及其抽象模型,数学基础等.咱们学习一个知识的时候通常有两个部分,抽象部分和形象部分,抽象比如语言的语法,是规则,形象比如具体的句子,能够用来和别人交流的.抽象比形象难于理解,但比形象通用.其实数学中常常碰到抽象和形象的概念,好比有一系列离散的点,这是形象;经过这些点咱们拟合出一条曲线,获得其函数,函数是抽象的;而后经过这个函数咱们能够获得更多的点,这又回到了形象上.因此学习任何知识不能仅仅停留在会用了,而要上升一个层次,去学习研究其抽象层次上的知识,抽象层度越高,则越通用.架构

一.基本概念

什么是Animation(动画),简单点说就是在一段时间内,显示的内容发生了变化.对CALayer来讲就是在一段时间内,其Animatable Property发生了变化.从CALayer(CA = Core Animation)类名来看就能够看出iOS的Layer就是为动画而生的,便于实现良好的交互体验. 这里涉及到两个东西: 一是Layer(基类CALayer),一是Animation(基于CAAnimation). Animation做用于Layer.CALayer提供了接口用于给本身添加Animation. 用于显示的Layer本质上讲是一个Model,包含了Layer的各类属性值. Animation则包含了动画的时间,变化,以及变化的速度.下面分别详细讲解Layer和Animation相关知识.app

二.CALayer及时间模型

咱们都知道UIView是MVC中的View.UIView的职责在于界面的显示和界面事件的处理.每个View的背后都有一个layer(能够经过view.layer进行访问),layer是用于界面显示的.CALayer属于QuartzCore框架,很是重要,但并无想象中的那么好理解.咱们一般操做的用于显示的Layer在Core Animation这层的概念中其实担当的是数据模型Model的角色,它并不直接作渲染的工做.关于Layer,以前从座标系的角度分析过,此次则侧重于它的时间系统.框架

1.Layer的渲染架构

Layer也和View同样存在着一个层级树状结构,称之为图层树(Layer Tree),直接建立的或者经过UIView得到的(view.layer)用于显示的图层树,称之为模型树(Model Tree),模型树的背后还存在两份图层树的拷贝,一个是呈现树(Presentation Tree),一个是渲染树(Render Tree). 呈现树能够经过普通layer(其实就是模型树)的layer.presentationLayer得到,而模型树则能够经过modelLayer属性得到(详情文档).模型树的属性在其被修改的时候就变成了新的值,这个是能够用代码直接操控的部分;呈现树的属性值和动画运行过程当中界面上看到的是一致的.而渲染树是私有的,你没法访问到,渲染树是对呈现树的数据进行渲染,为了避免阻塞主线程,渲染的过程是在单独的进程或线程中进行的,因此你会发现Animation的动画并不会阻塞主线程.ide

2.事务管理

CALayer的那些可用于动画的(Animatable)属性,称之为Animatable Properties,这里有一份详情的列表,罗列了全部的 CALayer Animatable Properties. 若是一个Layer对象存在对应着的View,则称这个Layer是一个Root Layer,非Root Layer通常都是经过CALayer或其子类直接建立的.下面的subLayer就是一个典型的非Root Layer,它没有对应的View对象关联着.函数

subLayer = [[CALayer alloc] init];  subLayer.frame = CGRectMake(0, 0, 300, 300);  subLayer.backgroundColor= [[UIColor redColor] CGColor];  [self.view.layer addSublayer:subLayer];oop

全部的非Root Layer在设置Animatable Properties的时候都存在着隐式动画,默认的duration是0.25秒.性能

subLayer.position = CGPointMake(300,400);学习

像上面这段代码当下一个RunLoop开始的时候并非直接将subLayer的position变成(300,400)的,而是有个移动的动画进行过渡完成的.

任何Layer的animatable属性的设置都应该属于某个CA事务(CATransaction),事务的做用是为了保证多个animatable属性的变化同时进行,不论是同一个layer仍是不一样的layer之间的.CATransaction也分两类,显式的和隐式的,当在某次RunLoop中设置一个animatable属性的时候,若是发现当前没有事务,则会自动建立一个CA事务,在线程的下个RunLoop开始时自动commit这个事务,若是在没有RunLoop的地方设置layer的animatable属性,则必须使用显式的事务.

显式事务的使用以下:

[CATransaction begin];
...
[CATransaction commit];

事务能够嵌套.当事务嵌套时候,只有当最外层的事务commit了以后,整个动画才开始.

能够经过CATransaction来设置一个事务级别的动画属性,覆盖隐式动画的相关属性,好比覆盖隐式动画的duration,timingFunction.若是是显式动画没有设置duration或者timingFunction,那么CA事务设置的这些参数也会对这个显式动画起做用.

还能够设置completionBlock,当当前CATransaction的全部动画执行结束后, completionBlock会被调用.

3.时间系统

CALayer实现了CAMediaTiming协议. CALayer经过CAMediaTiming协议实现了一个有层级关系的时间系统.除了CALayer,CAAnimation也采纳了此协议,用来实现动画的时间系统. 
在CA中,有一个Absolute Time(绝对时间)的概念,能够经过CACurrentMediaTime()得到,其实这个绝对时间就是将mach_absolute_time()转换成秒后的值.这个时间和系统的uptime有关,系统重启后CACurrentMediaTime()会被重置. 
就和座标存在相对座标同样,不一样的实现了CAMediaTiming协议的存在层级关系的对象也存在相对时间,常常须要进行时间的转换,CALayer提供了两个时间转换的方法:

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

如今来重点研究CAMediaTiming协议中几个重要的属性.

beginTime

不管是图层仍是动画,都有一个时间线Timeline的概念,他们的beginTime是相对于父级对象的开始时间. 虽然苹果的文档中没有指明,可是经过代码测试能够发现,默认状况下全部的CALayer图层的时间线都是一致的,他们的beginTime都是0,绝对时间转换到当前Layer中的时间大小就是绝对时间的大小.因此对于图层而言,虽然建立有前后,可是他们的时间线都是一致的(只要不主动去修改某个图层的beginTime),因此咱们能够想象成全部的图层默认都是从系统重启后开始了他们的时间线的计时.

可是动画的时间线的状况就不一样了,当一个动画建立好,被加入到某个Layer的时候,会先被拷贝一份出来用于加入当前的图层,在CA事务被提交的时候,若是图层中的动画的beginTime为0,则beginTime会被设定为当前图层的当前时间,使得动画当即开始.若是你想某个直接加入图层的动画稍后执行,能够经过手动设置这个动画的beginTime,但须要注意的是这个beginTime须要为 CACurrentMediaTime()+延迟的秒数,由于beginTime是指其父级对象的时间线上的某个时间,这个时候动画的父级对象为加入的这个图层,图层当前的时间其实为[layer convertTime:CACurrentMediaTime() fromLayer:nil],其实就等于CACurrentMediaTime(),那么再在这个layer的时间线上日后延迟必定的秒数便获得上面的那个结果.

timeOffset

这个timeOffset多是这几个属性中比较难理解的一个,官方的文档也没有讲的很清楚. local time也分红两种一种是active local time 一种是basic local time.
timeOffset则是active local time的偏移量. 
你将一个动画看做一个环,timeOffset改变的实际上是动画在环内的起点,好比一个duration为5秒的动画,将timeOffset设置为2(或者7,模5为2),那么动画的运行则是从原来的2秒开始到5秒,接着再0秒到2秒,完成一次动画.

speed

speed属性用于设置当前对象的时间流相对于父级对象时间流的流逝速度,好比一个动画beginTime是0,可是speed是2,那么这个动画的1秒处至关于父级对象时间流中的2秒处. speed越大则说明时间流逝速度越快,那动画也就越快.好比一个speed为2的layer其全部的父辈的speed都是1,它有一个subLayer,speed也为2,那么一个8秒的动画在这个运行于这个subLayer只需2秒(8 / (2 * 2)).因此speed有叠加的效果.

fillMode

fillMode的做用就是决定当前对象过了非active时间段的行为. 好比动画开始以前,动画结束以后。若是是一个动画CAAnimation,则须要将其removedOnCompletion设置为NO,要否则fillMode不起做用. 下面来说各个fillMode的意义 
kCAFillModeRemoved 这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到以前的状态 
kCAFillModeForwards 当动画结束后,layer会一直保持着动画最后的状态 
kCAFillModeBackwards 这个和kCAFillModeForwards是相对的,就是在动画开始前,你只要将动画加入了一个layer,layer便当即进入动画的初始状态并等待动画开始.你能够这样设定测试代码,将一个动画加入一个layer的时候延迟5秒执行.而后就会发如今动画没有开始的时候,只要动画被加入了layer,layer便处于动画初始状态 
kCAFillModeBoth 理解了上面两个,这个就很好理解了,这个其实就是上面两个的合成.动画加入后开始以前,layer便处于动画初始状态,动画结束后layer保持动画最后的状态.

其余的一些参数都是比较容易理解的.

实际应用

参见苹果官方 QA1673 How to pause the animation of a layer tree

-(void)pauseLayer:(CALayer*)layer{  CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime()fromLayer:nil];  layer.speed = 0.0;  layer.timeOffset = pausedTime;}-(void)resumeLayer:(CALayer*)layer{CFTimeInterval pausedTime = [layer timeOffset];  layer.speed = 1.0;  layer.timeOffset = 0.0;  layer.beginTime= 0.0;  CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] -pausedTime;  layer.beginTime = timeSincePause;}

三.显式动画Animation

当须要对非Root Layer进行动画或者须要对动画作更多自定义的行为的时候,就必须使用到显式动画了,显式动画的基类为CAAnimation,经常使用的是CABasicAnimation,CAKeyframeAnimation有时候还会使用到CAAnimationGroup,CATransition(注意不是CATransaction,Transition是过渡的意思). 
图片

这里再强调关于动画的两个重要的点:一是中间状态的插值计算(Interpolation),二是动画节奏控制(Timing); 有时候插值计算也和Timing有必定关系. 若是状态是一维空间的值(好比透明度),那么插值计算的结果必然再起点值和终点值之间,若是状态是二维空间的值(好比position),那么通常状况下插值获得的点会落在起点和终点之间的线段上(固然也有可能连线是圆滑曲线).

1.CABasicAnimation

不论是CABasicAnimation仍是CAKeyframeAnimation都是继承于CAPropertyAnimation. 
图片

 
CABasicAnimation有三个比较重要的属性,fromValue,toValue,byValue,这三个属性都是可选的,但不能同时多于两个为非空.最终都是为了肯定animation变化的起点和终点.Setting Interpolation Values详细介绍了这个三个值的各类状况以及用途. 设置了动画的起点和终点以后,中间的值都是经过插值方式计算出来的.插值计算的结果由timingFunction指定,默认timingFunction为nil,会使用liner的,也就是变化是均匀的.

2.Timing Function的做用

Timing Function的会被用于变化起点和终点之间的插值计算.形象点说是Timing Function决定了动画运行的节奏(Pacing),好比是均匀变化(相同时间变化量相同),先快后慢,先慢后快仍是先慢再快再慢.

时间函数是使用的一段函数来描述的,横座标是时间t取值范围是0.0-1.0,纵座标是变化量x(t)也是取值范围也是0.0-1.0 假设有一个动画,duration是8秒,变化值的起点是a终点是b(假设是透明度),那么在4秒处的值是多少呢? 能够经过计算为 a + x(4/8) * (b-a), 为何这么计算呢?讲实现的时间映射到单位值的时候4秒相对于总时间8秒就是0.5而后能够获得0.5的时候单位变化量是 x(0.5), x(0.5)/1 = 实际变化量/(b-a), 其中b-a为总变化量,因此实际变化量就是x(0.5) * (b-a) ,最后4秒时的值就是 a + x(0.5) * (b-a),因此计算的本质是映射.

Timing Function对应的类是CAMediaTimingFunction,它提供了两种得到时间函数的方式,一种是使用预约义的五种时间函数,一种是经过给点两个控制点获得一个时间函数. 相关的方法为

+ (id)functionWithName:(NSString *)name;+ (id)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;- (id)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y;

五种预约义的时间函数名字的常量变量分别为 
kCAMediaTimingFunctionLinear, 
kCAMediaTimingFunctionEaseIn, 
kCAMediaTimingFunctionEaseOut, 
kCAMediaTimingFunctionEaseInEaseOut, 
kCAMediaTimingFunctionDefault. 
下图展现了前面四种Timing Function的曲线图,横座标表示时间,纵座标表示变化量,这点须要搞清楚(并非平面座标系中xy). 
图片
 

自定义的Timing Function的函数图像就是一条三次贝塞尔曲线Cubic Bezier Curve,贝塞尔曲线的优势就是光滑,用在这里就使得变化显得光滑.一条三次贝塞尔曲线能够由起点终点以及两个控制点决定. 
上面的kCAMediaTimingFunctionDefault对应的函数曲线其实就是经过[(0.0,0.0), (0.25,0.1), (0.25,0.1), (1.0,1.0)]这四个点决定的三次贝塞尔曲线,头尾为起点和终点,中间的两个点是控制点. 
图片
上图中P0是起点,P3是终点,P1和P2是两个控制点

若是时间变化曲线既不是直线也不是贝塞尔曲线,而是自定义的,又或者某个图层运动的轨迹不是直线而是一个曲线,这些是基本动画没法作到的,因此引入下面的内容,CAKeyframeAnimation,也即所谓的关键帧动画.

3.CAKeyframeAnimation

任何动画要表现出运动或者变化,至少须要两个不一样的关键状态,而中间的状态的变化能够经过插值计算完成,从而造成补间动画,表示关键状态的帧叫作关键帧. CABasicAnimation其实能够看做一种特殊的关键帧动画,只有头尾两个关键帧.CAKeyframeAnimation则能够支持任意多个关键帧,关键帧有两种方式来指定,使用path或者使用values,path是一个CGPathRef的值,且path只能对CALayer的 anchorPoint 和 position 属性起做用,且设置了path以后values就再也不起效了.而values则更加灵活. keyTimes这个可选参数能够为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平分的. 
还能够经过设置可选参数timingFunctions(CAKeyframeAnimation中timingFunction是无效的)为关键帧之间的过渡设置timingFunction,若是values有n个元素,那么timingFunctions则应该有n-1个.但不少时候并不须要timingFunctions,由于已经设置了够多的关键帧了,好比没1/60秒就设置了一个关键帧,那么帧率将达到60FPS,彻底不须要相邻两帧的过渡效果(固然也有可能某两帧 值相距较大,可使用均匀变化或者增长帧率,好比每0.01秒设置一个关键帧).

在关键帧动画中还有一个很是重要的参数,那即是calculationMode,计算模式.其主要针对的是每一帧的内容为一个座标点的状况,也就是对anchorPoint 和 position 进行的动画.当在平面座标系中有多个离散的点的时候,能够是离散的,也能够直线相连后进行插值计算,也可使用圆滑的曲线将他们相连后进行插值计算. calculationMode目前提供以下几种模式 kCAAnimationLinear 
kCAAnimationDiscrete 
kCAAnimationPaced 
kCAAnimationCubic 
kCAAnimationCubicPaced

kCAAnimationLinear calculationMode的默认值,表示当关键帧为座标点的时候,关键帧之间直接直线相连进行插值计算; 
kCAAnimationDiscrete 离散的,就是不进行插值计算,全部关键帧直接逐个进行显示; 
kCAAnimationPaced 使得动画均匀进行,而不是按keyTimes设置的或者按关键帧平分时间,此时keyTimes和timingFunctions无效; 
kCAAnimationCubic 对关键帧为座标点的关键帧进行圆滑曲线相连后插值计算,对于曲线的形状还能够经过tensionValues,continuityValues,biasValues来进行调整自定义,这里的数学原理是Kochanek–Bartels spline,这里的主要目的是使得运行的轨迹变得圆滑; 
kCAAnimationCubicPaced 看这个名字就知道和kCAAnimationCubic有必定联系,其实就是在kCAAnimationCubic的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的.

最后推荐下WWDC 2010和2011上的关于Animation相关的Session,你们能够找找来看.2010的有说到Core Graphic相关内容.以及他们都从性能方面对CA作了些诠释.

相关文章
相关标签/搜索