封面设计软件-收藏夹打不开

cakeyframeanimation
2023年4月1日发(作者:如何用u盘重装系统)

谈谈iOSAnimation(转载)

零.前⾔

这⾥没有太多的代码细节,只是探索iOS动画的基本概念,以及其抽象模型,数学基础等.我们学习⼀个知识的时候⼀般有两个部分,抽象部分和形象部分,

抽象好⽐语⾔的语法,是规则,形象好⽐具体的句⼦,可以⽤来和别⼈交流的.抽象⽐形象难于理解,但⽐形象通⽤.其实数学中经常碰到抽象和形象的概

念,⽐如有⼀系列离散的点,这是形象;通过这些点我们拟合出⼀条曲线,得到其函数,函数是抽象的;然后通过这个函数我们可以得到更多的点,这⼜回到

了形象上.所以学习任何知识不能仅仅停留在会⽤了,⽽要上升⼀个层次,去学习研究其抽象层次上的知识,抽象层度越⾼,则越通⽤.

⼀.基本概念

什么是Animation(动画),简单点说就是在⼀段时间内,显⽰的内容发⽣了变化.对CALayer来说就是在⼀段时间内,其AnimatableProperty发⽣了

变化.从CALayer(CA=CoreAnimation)类名来看就可以看出iOS的Layer就是为动画⽽⽣的,便于实现良好的交互体验.这⾥涉及到两个东西:⼀

是Layer(基类CALayer),⼀是Animation(基于CAAnimation).Animation作⽤于r提供了接⼝⽤于给⾃⼰添加Animation.⽤于显

⽰的Layer本质上讲是⼀个Model,包含了Layer的各种属性值.Animation则包含了动画的时间,变化,以及变化的速度.下⾯分别详细讲解Layer和

Animation相关知识.

⼆.CALayer及时间模型

我们都知道UIView是MVC中的的职责在于界⾯的显⽰和界⾯事件的处理.每⼀个View的背后都有⼀个layer(可以通过进⾏

访问),layer是⽤于界⾯显⽰的.CALayer属于QuartzCore框架,⾮常重要,但并没有想象中的那么好理解.我们通常操作的⽤于显⽰的Layer在Core

Animation这层的概念中其实担当的是数据模型Model的⾓⾊,它并不直接做渲染的⼯作.关于Layer,之前从座标系的⾓度分析过,这次则侧重于它的

时间系统.

的渲染架构

Layer也和View⼀样存在着⼀个层级树状结构,称之为图层树(LayerTree),直接创建的或者通过UIView获得的()⽤于显⽰的图层树,称之

为模型树(ModelTree),模型树的背后还存在两份图层树的拷贝,⼀个是呈现树(PresentationTree),⼀个是渲染树(RenderTree).呈现树可以通过

普通layer(其实就是模型树)的tationLayer获得,⽽模型树则可以通过modelLayer属性获得(详情⽂档).模型树的属性在其被修改的时

候就变成了新的值,这个是可以⽤代码直接操控的部分;呈现树的属性值和动画运⾏过程中界⾯上看到的是⼀致的.⽽渲染树是私有的,你⽆法访问到,

渲染树是对呈现树的数据进⾏渲染,为了不阻塞主线程,渲染的过程是在单独的进程或线程中进⾏的,所以你会发现Animation的动画并不会阻塞主线

程.

2.事务管理

subLayer=[[CALayeralloc]init];

=CGRectMake(0,0,300,300);

oundColor=[[UIColorredColor]CGColor];

[ddSublayer:subLayer];

所有的⾮RootLayer在设置AnimatableProperties的时候都存在着隐式动画,默认的duration是0.25秒.

on=CGPointMake(300,400);

像上⾯这段代码当下⼀个RunLoop开始的时候并不是直接将subLayer的position变成(300,400)的,⽽是有个移动的动画进⾏过渡完成的.

任何Layer的animatable属性的设置都应该属于某个CA事务(CATransaction),事务的作⽤是为了保证多个animatable属性的变化同时进⾏,不管

是同⼀个layer还是不同的layer之间的.CATransaction也分两类,显式的和隐式的,当在某次RunLoop中设置⼀个animatable属性的时候,如果发现

当前没有事务,则会⾃动创建⼀个CA事务,在线程的下个RunLoop开始时⾃动commit这个事务,如果在没有RunLoop的地⽅设置layer的

animatable属性,则必须使⽤显式的事务.

显式事务的使⽤如下:

[CATransactionbegin];

...

[CATransactioncommit];

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

可以通过CATransaction来设置⼀个事务级别的动画属性,覆盖隐式动画的相关属性,⽐如覆盖隐式动画的duration,timingFunction.如果是显式动

画没有设置duration或者timingFunction,那么CA事务设置的这些参数也会对这个显式动画起作⽤.

还可以设置completionBlock,当当前CATransaction的所有动画执⾏结束后,completionBlock会被调⽤.

3.时间系统

CALayer实现了CAMediaTiming协议.CALayer通过CAMediaTiming协议实现了⼀个有层级关系的时间系统.除了CALayer,CAAnimation也

采纳了此协议,⽤来实现动画的时间系统.在CA中,有⼀个AbsoluteTime(绝对时间)的概念,可以通过CACurrentMediaTime()获得,其实这个绝对

时间就是将mach_absolute_time()转换成秒后的值.这个时间和系统的uptime有关,系统重启后CACurrentMediaTime()会被重置.就和座标存在

相对座标⼀样,不同的实现了CAMediaTiming协议的存在层级关系的对象也存在相对时间,经常需要进⾏时间的转换,CALayer提供了两个时间转换

的⽅法:

-(CFTimeInterval)convertTime:(CFTimeInterval)tfromLayer:(CALayer*)l;

-(CFTimeInterval)convertTime:(CFTimeInterval)ttoLayer:(CALayer*)l;

现在来重点研究CAMediaTiming协议中⼏个重要的属性.

beginTime

⽆论是图层还是动画,都有⼀个时间线Timeline的概念,他们的beginTime是相对于⽗级对象的开始时间.虽然苹果的⽂档

中没有指明,但是通过代码测试可以发现,默认情况下所有的CALayer图层的时间线都是⼀致的,他们的beginTime都是0,绝

对时间转换到当前Layer中的时间⼤⼩就是绝对时间的⼤⼩.所以对于图层⽽⾔,虽然创建有先后,但是他们的时间线都是⼀

致的(只要不主动去修改某个图层的beginTime),所以我们可以想象成所有的图层默认都是从系统重启后开始了他们的时间

线的计时.

但是动画的时间线的情况就不同了,当⼀个动画创建好,被加⼊到某个Layer的时候,会先被拷贝⼀份出来⽤于加⼊当前的图

层,在CA事务被提交的时候,如果图层中的动画的beginTime为0,则beginTime会被设定为当前图层的当前时间,使得动画

⽴即开始.如果你想某个直接加⼊图层的动画稍后执⾏,可以通过⼿动设置这个动画的beginTime,但需要注意的是这个

beginTime需要为CACurrentMediaTime()+延迟的秒数,因为beginTime是指其⽗级对象的时间线上的某个时间,这个时

候动画的⽗级对象为加⼊的这个图层,图层当前的时间其实为[layerconvertTime:CACurrentMediaTime()

fromLayer:nil],其实就等于CACurrentMediaTime(),那么再在这个layer的时间线上往后延迟⼀定的秒数便得到上⾯的

那个结果.

timeOffset

这个timeOffset可能是这⼏个属性中⽐较难理解的⼀个,官⽅的⽂档也没有讲的很清楚.localtime也分成两种⼀种是

activelocaltime⼀种是fset则是activelocaltime的偏移量.你将⼀个动画看作⼀个

环,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保持动画最后的状态.

其他的⼀些参数都是⽐较容易理解的.

-(void)pauseLayer:(CALayer*)layer{

CFTimeIntervalpausedTime=[layerconvertTime:CACurrentMediaTime()fromLayer:nil];

=0.0;

fset=pausedTime;

}

-(void)resumeLayer:(CALayer*)layer{

CFTimeIntervalpausedTime=[layertimeOffset];

=1.0;

fset=0.0;

ime=0.0;

CFTimeIntervaltimeSincePause=[layerconvertTime:CACurrentMediaTime()fromLayer:nil]-pausedTime;

ime=timeSincePause;

}

三.显式动画Animation

当需要对⾮RootLayer进⾏动画或者需要对动画做更多⾃定义的⾏为的时候,就必须使⽤到显式动画了,显式动画的基类为CAAnimation,常⽤的是

CABasicAnimation,CAKeyframeAnimation有时候还会使⽤到CAAnimationGroup,CATransition(注意不是CATransaction,Transition是

过渡的意思).

这⾥再强调关于动画的两个重要的点:⼀是中间状态的插值计算(Interpolation),⼆是动画节奏控制(Timing);有时候插值计算也和Timing有⼀定关

系.如果状态是⼀维空间的值(⽐如透明度),那么插值计算的结果必然再起点值和终点值之间,如果状态是⼆维空间的值(⽐如position),那么⼀般情况

下插值得到的点会落在起点和终点之间的线段上(当然也有可能连线是圆滑曲线).

cAnimation

不管是CABasicAnimation还是CAKeyframeAnimation都是继承于CAPropertyAnimation.

Function的作⽤

TimingFunction的会被⽤于变化起点和终点之间的插值计算.形象点说是TimingFunction决定了动画运⾏的节奏(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),所以计算的本质是映射.

TimingFunction对应的类是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.下图展⽰了前⾯四

种TimingFunction的曲线图,横座标表⽰时间,纵座标表⽰变化量,这点需要搞清楚(并不是平⾯座标系中xy).

上图中P0是起点,P3是终点,P1和P2是两个控制点

如果时间变化曲线既不是直线也不是贝塞尔曲线,⽽是⾃定义的,⼜或者某个图层运动的轨迹不是直线⽽是⼀个曲线,这些是基本动画⽆法做到的,所以

引⼊下⾯的内容,CAKeyframeAnimation,也即所谓的关键帧动画.

rameAnimation

任何动画要表现出运动或者变化,⾄少需要两个不同的关键状态,⽽中间的状态的变化可以通过插值计算完成,从⽽形成补间动画,表⽰关键状态的

帧叫做关键帧.

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

最后推荐下WWDC2010和2011上的关于Animation相关的Session,⼤家可以找找来看.2010的有说到CoreGraphic相关内容.以及他们都从性

能⽅⾯对CA做了些诠释.

更多推荐

cakeyframeanimation