core animation迟来的总结 CABaseAnimation CAPropertyAnimation CAKeyframeAnimation CAMediaTiming

从事iOS开发这么长时间,动画,交互效果也写了不少,Core Animation对于我来说并不陌生,但是,对于一些不常用的属性,理解的却并不是很深刻,所以最近抽时间把Core Animation又熟悉了一遍,本篇文章算是对Core Animation的一份迟来的总结

不过这篇文章,仍然不会是一篇入门文章。

其实说是讨论Core Animation,我实际上讨论的是QuartzCore Framework,这个框架最重要的概念就是各种layer,以及应用到layer上的动画

屏幕中多个layer一层一层叠加,形成了Layer Tree(图层树),而iOS,同时维护着三个Layer Tree即
Model layer tree,Presentation tree,Render tree

  • Model tree中的layer就是我们通常接触到的layer,当我们修改layer的属性的时候,就会立即修改Model tree中的layer的值,比如layer.position = CGPointMake(0,0); 这里的修改会直接影响Model tree中的layer的postion值
  • Presentation tree 反映Layer在屏幕中的真实位置,所以叫”呈现树”,比如我们创建一个CABaseAnimation将layer移动到(100,100),layer原始位置(0,0),在动画中途,访问layer.position(Model tree),得到(0,0)但是访问layer.presentationLayer.position(Presentation tree),得到的值则是屏幕中layer的实际位置。CABaseAnimation注动画并不会改变layer的实际值(Model tree)
  • Render tree 渲染树是私有的,你无法访问到,渲染树是对呈现树的数据进行渲染,为了不阻塞主线程,渲染的过程是在单独的进程或线程中进行的,所以你会发现Animation的动画并不会阻塞主线程。
    渲染树是CoreAnimation内部的功能,我们基本不会遇到它,而模型树和呈现树却经常会碰到。

CABaseAnimation

基础动画类,提供了fromValue,toValue,byValue这种起止值的简单配置,配合keyPath能够使相应的属性,在fromValue,toValue,byValue值之间差值动画

fromValue,toValue,byValue

fromValue指定开始的值
toValue指定结束的值
byValue指定在目前的状态上叠加的值

下面是三个value不同的组合状态下,差值是如何计算的

只有fromValue有值, 在fromValue与当前 presentation value(呈现树)的值之间差值
只有toValue有值, 在current value of the property in the render tree(当前render tree中的属性值)与toValue之间差值
只有byValue有值,在current value of the property in the render tree与current value of the property in the render tree+byValue之间差值

fromValue与byValue有值,在fromValue与fromValue+byValue之间差值
byValye与toValue有值,在toValue-byValue与toValue之间差值

CAPropertyAnimation

这个类是 CABaseAnimation,CAKeyframeAnimation的基类,只提供了keyPath,additive,cumulative,valueFunction几个属性。其中keyPath不必多说,主要理解additive,cumulative两个属性

additive

是否叠加,设置additive之后会将动画设定的value叠加到layer当前(动画前)presentation tree 中的值
比如 CABaseAnimation fromValue=(0,100) toValue=(100,100) layer的位置(0,300)
当additive=false 的时候很好理解, 此次动画的presentation 从(0,100)到(100,100) 动画开始layer从(0,300)闪到(0,100)然后动画到(100,100)

当additive=yes 的时候,layer当前presentation Value=(0,300) 叠加fromValue后 动画初始presentation Value=(0,400), 叠加toValue后 动画结束presentation Value=(100,400), 注意这里的叠加都是相对最开始的presentation Value=(0,300)
所以此次动画的presentation 从(0,400)到(100,400),动画开始layer 闪现到(0,400)然后开始动画到(100,400)

所以如果想让一个view 往左移动100,往右移动100,一般我们有两种做法(CAKeyFrameAnimation)
1.获取view的位置postion1,然后计算其左边100的positionLeft,计算positionRight
[email protected][position1,positionleft,positionRight]
2.不需要知道view的位置,使用additive=yes
[email protected][(0,0),@(-100,0),@(100,0)];

注意additive=yes的时候是对layer未进行动画前的presentation tree中的值进行叠加

假设上例中 layer=(200,200)
方法2中得到的三个阶段的 presentation值
(200+0,200+0)=(200,200)
(200-100,200+0)=(100,200)
(200+100,200+0)=(300,200)
如果 不是对未进行动画前的presentation(200,200)进行叠加,而是对前一个阶段的presentation 进行叠加
(200+0,200+0)=(200,200)
(200-100,200+0)=(100,200)
(100+100,200+0)=(200,200) 第三次回到初识位置,而不是去右边100
观察动画,确认是第一种情况 印证了使用的是未进行动画前的presentation值进行叠加

cumulative

The `cumulative’ property affects how repeating animations produce their result

只对repeaCount>1 的动画有效,当前动画的值=上一次循环动画结束的值+动画本身设置的值

比如 layer动画 fromValue=(100,100) toValue=(200,200) 循环次数2
第一次循环 (100,100)-(200,200)
第二次唇环 (200+100,200+100) - (200+200,200+200)
所以会看到 第二次循环的时候,动画有一个跳跃,从 (200,200)跳到(300,300)
一般设置cumulative的时候,最好初始值设置为0
比如从0度旋转到180度, repeatCount=2 cumulative=YES 就是旋转360度
如果从30度 旋转到180度 repeatCount=2 cumulative=YES 那么,第二次就是有一个跳动 (从180突然跳到210)然后再开始旋转

CAKeyframeAnimation

CAKeyframeAnimation相对于CABaseAnimation,在动画的控制上更加精细,我们可以指定一组value来对属性进行动画。value上面的每个值也就称为关键帧

values keytimes

values与keytimes是一一对应的,keytimes为[0-1]的区间

timingFuctions

时间函数,一般动画是线性差值,即整个动画是匀速的,我们也可以改变差值函数,来时动画达到变速效果,比如常见的easeIn,easeOut,bounds等等

注意timingFuctions是要比values和keytimes少一项的,因为timingFuctions描述的是一个区间的曲线,比如values=[a,b,c]那么timingFuctions=[x,y] a到b 应用x曲线 b到c 应用y曲线
当然也可以整个动画使用一个曲线,需要配置timingFunction

path

此path不是keyPath主意区分,一般用来做position动画

可以指定一个CGPath,一般指定一个贝塞尔曲线,当其指定的时候values被忽略
layer会沿着path的路径运动。并且取path上面的几个点做区间
比如path是一个矩形,那么就会去四个角的点,做为区间,我们可以定义相应的keytimes timingFuctions
比如path是一个圆形 那么 0,90,180,270,360度的圆上的四个切点为区间,我们可以定义相应的keytimes timingFuctions
比如path是一个自定义折线,类似下面这种
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(0, 100)];
每两个点是一个区间,等价于values=[(0,0),(0,100)…..]

calculationMode

动画的计算模式

const kCAAnimationLinear//线性,默认
const kCAAnimationDiscrete//离散,无中间过程,但keyTimes设置的时间依旧生效,物体跳跃地出现在各个关键帧上
const kCAAnimationPaced//平均,keyTimes跟timeFunctions失效
const kCAAnimationCubic//三次曲线插值 ,但keyTimes设置的时间依旧生效
const kCAAnimationCubicPaced//三次曲线插值平均,keyTimes跟timeFunctions失效

timingFunction是控制动画在时间上的曲线,calculationMode则是控制动画在value上的曲线,
比如动画移动路径为一个矩形,如果设置kCAAnimationCubic,那么,在经历四个顶点的时候,是圆滑移动的,不是走的直角

rotationMode

旋转模式,默认nil,不旋转

kCAAnimationRotateAuto :rotate to match the path tangent(切线)
kCAAnimationRotateAutoReverse : rotates to match the tangent +180 degrees

CAMediaTiming

CALayer实现了CAMediaTiming协议. CALayer通过CAMediaTiming协议实现了一个有层级关系的时间系统.除了CALayer,CAAnimation也实现了此协议,用来实现动画的时间系统.
在CA中,有一个Absolute Time(绝对时间)的概念,可以通过CACurrentMediaTime()获得,其实这个绝对时间就是将mach_absolute_time()转换成秒后的值.这个时间和系统的uptime有关,系统重启后CACurrentMediaTime()会被重置.

每个layer都有自己的local time 用来管理animation timing,一般两个不同的layer的local time 几乎是一样的,但是 一个layer的local time 会被 其parent layer 改变,或者被这个layer自己的timing parameters 改变,比如,修改layer的speed(timing parameters) 引起这个layer上的animations 的duration改变,同时也会使这个layer上面的subLayer上的动画duration改变

获取一个layer 的local time

1
CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];

一旦你有某个layer的local time中的某个time value ,你就能够用这个time value 更新那些 animation object或者layer的 timing-related 的属性.

local time 又分为两种,
active local time : 当动画被激活的时间段是active local time
base local time :
timeOffset属性 只在active local time 中生效

begainTime

最简单的理解就是动画的开始时间,一般我们将动画添加到layer上动画会立即开始,如果我们设置了动画的begainTime,那么动画就会在我们设定的时间开始。

但是这个开始时间是相对哪个时间呢,是相对世界时间,还是相对于0(从动画添加到layer上的那一刻,认为是0)

如果是相对于世界时间,那么begainTime就要设置为当前时间戳+秒
如果是相对于0 那么begainTime就要设置为秒数就可以了

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

另外 CAAminationGroup中的子animation的begainTime可以是0-xx 这种秒数,但是这个group的beginTime必须设置为CACurrentMediaTime()+xxx这种
直接add到layer上面的animation 的beginTime 必须设置为CACurrentMediaTime()+xxx

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秒,完成一次动画.

1
localTime=(parentTime-begainTime)*speed+offset

我们再联合 begainTime 举一个例子
比如动画设定 2秒 从(0,0)-(200,200)
begainTime设置为 CACurrentMediaTime()+2.0 timeoffset设置为1.0
则在CACurrentMediaTime()+2.0 之前,不属于active local time 所以timeOffset在2.0秒之前不生效
parentTime : 0------1.0-------2.0--------------3.0
localTime : not active--not active--- active(2.0-2.0)*speed+1.0=1.0----2.0
动画的效果就是 开始两秒是不动的(not active local time),然后开始从(100,100)开始动画,到(200,200),然后再回到(0,0)动画到(100,100)

一般有两种用途,
1.speed=0 通过设置timeOffset 来控制动画进度
2.group中的animations 设置不同的offset 会产生动画延迟效果(注意跟设置beginTime 原理不一样)

fillMode

指定当前对象,在没有激活动画时候的行为,一般就两个时间点, 动画激活前,动画激活后,需要设置removedOnCompletion=NO才生效

kCAFillModeRemoved 动画开始前,结束后对layer 都没有影响,也就是说,动画开始前,layer不会在动画的开始位置,动画结束后,layer不会停在结束位置
kCAFillModeForwards 动画结束后 layer 会一直保持着动画的结束状态
kCAFillModeBackwards 动画开始前 只要对layer addanimation layer 就会进入动画的初始状态等待开始
kCAFillModeBoth kCAFillModeForwards+kCAFillModeBackwards

kCAFillModeForwards 即使动画结束 layer停在结束状态,layer的model value 还是不会改变

kCAFillModeBackwards 一般addAnimation之后,如果object的位置与动画的开始值不一致,object会变为开始值,那kCAFillModeBackwards有什么用呢, 只需要设置一下begainTime=CACurrentMediaTime()+1.0 就能看出来了,让动画延迟1秒,如果没有设置kCAFillModeBackwards ,1秒后object才进入初始状态,如果设置kCAFillModeBackwards之后,在addAnimation之后,马上进入了初始状态

CAMediaTimingFunction

系统默认内置了一些CAMediaTimingFunction,我们也可以通过配置一个贝塞尔曲线的两个控制点来自己定义一条曲线

1
[CAMediaTimingFunction functionWithControlPoints:1.00 :0 :0.35 :1.0]

这个时间线是我们自定义的贝塞尔曲线, 起点(0,0) 终点(1,1) 然后 上面四个float 对应c1x,c1y c2x,c2y,这样调整出一个贝塞尔曲线,做动画的时间线