一、CATransform3D是什么?
CATransform3D这个结构体表示三维的齐次坐标变换矩阵.
齐次坐标是一种坐标的表示方法,
n维空间的坐标需要用n+1个元素的坐标元组来表示,
在Quartz 2D Transform中就有关于齐次坐标的应用,
那里是关于二维空间的变换,
其某点的齐次坐标的最后一个元素始终设置为1。
二、为什么需要齐次坐标
之所以要使用齐次坐标,而不是简单的数学坐标
目的是:为了方便图形进行仿射变换,
仿射变换可以通过仿射变换矩阵来实现,
3D的仿射变换功能强大,它可以实现诸如:
平移(translation),旋转(rotation),缩放(scaling),切变(shear)等
如果不用齐次坐标那么进行坐标变换可能就涉及到两种运算了:
加法(平移)和乘法(旋转,缩放)
然而,使用齐次坐标以及齐次坐标变换矩阵后,问题就简单多了
只需要矩阵乘法就可以完成一切了。
以下需要线性代数、图形变换的相关知识,进行矩阵的乘法。
三、CATransform3D中的最关键的m34
iOS中的CALayer的3D本质上并不能算真正的3D
因为其视点即观察点或者所谓的照相机的位置是无法变换的,
而仅仅只是3D在二维平面上的投影,
投影平面就是:手机屏幕
也就是xy轴组成的UIKit坐标平面(注意UIKit中为左手坐标系),
那么视点的位置是如何确定的呢?
可以通过CATransform3D中的m34来间接指定:
m34是相当常用的一个属性
m34 = -1/z,
其中z为观察点在z轴上的值,
下面代码是XCode文档中摘取的
1 CATransform3D aTransform = CATransform3DIdentity;2 // the value of zDistance affects the sharpness of the transform.3 zDistance = 850;4 aTransform.m34 = 1.0 / -zDistance;
而Layer的z轴的位置则是通过anchorPoint来指定的,
所谓的anchorPoint(锚点)就是在变换中保持不变的点,
也就是某个Layer在变换中的原点,
xyz三轴相交于此原点(锚点)。
在iOS中,Layer的anchorPoint使用单位坐标系来描述,
单位坐标系,范围是0~1
无需指定具体真实的坐标点,
而是使用layer bounds中的相对位置,
下图展示了一个Layer中的几个特殊的锚点, 默认是(0.5,0.5)
在上面的:
m34 = -1/z中,
当z值为正的时候(如上面的850),
是我们人眼观察现实世界的效果,
即在投影平面上表现出近大远小的效果,
z越靠近原点则这种效果越明显,越远离原点则越不明显,
当z为正无穷大的时候,则失去了近大远小的效果,
此时投影线垂直于投影平面,也就是视点在无穷远处,
CATransform3D中m34的默认值为0,即视点在无穷远处.
特别注意的是:
齐次坐标到数学坐标的转换
通用的齐次坐标为 (a, b, c, h),
其转换成数学坐标则为 (a/h, b/h, c/h).
四.代数解释
假设一个Layer anchorPoint为默认的 (0.5, 0.5 ),
其三维空间中一个A点 (6, 0, 0),z=1000
m34 = -1/z = -1/1000.0,
则此点往z轴负方向移动10个单位之后,
则在投影平面上看到的点的坐标是多少呢?
上面的两个矩阵相乘则会得到最终的变换矩阵
所以一个矩阵就可以完成变换和投影。
将A点坐标乘上最终的变换矩阵,
则得到 {6, 0 , -10, 1.01},
转换成数学坐标点为 {6/1.01, 0, 10/1.01},
则可以知道其在投影平面上的投影点为 {6/1.01, 0, 0}
也就是我们看到的变换后的点。
其比之前较靠近原点。
越往z轴负方向移动,则在投影平面上越靠近原点。
五.几何作图解释
将上面的例子使用几何的方式来进行解释分析,
当人的眼睛 沿着y轴的正方向向下看时候,可以得到如下的景象
此时y轴垂直于屏幕指向人的眼晴
虚线为投影线,其和x轴的交点即为A点的投影点。
由相似三角形的定理算出投影的点,
1000/(1000 + 10) = x/6,则x = 6*1000/1010 = 6/1.01
iOS设备给用户视觉反馈其实都是通过QuartzCore框架来进行的,用户最终看到的显示界面都是图层合成的结果,
而图层即是QuartzCore中的CALayer。
通常我们所说的视图即UIView,并不是直接显示在屏幕上,
而是在创建UIView对象的时候会自动创建一个CALayer层(根层),
而视图对象把要显示的东西绘制在层上,待到需要显示时硬件将所有的层拷贝,
最后,按Z轴的高低合成最终的显示结果。
CALayer本质上是一块包含一幅位图的缓冲区,由视图创建的根层为隐式层,而手动创建的层称为显示层。
所有的动画效果都是通过CAAnimation类的子类来完成的。
(因为CAAnimation是抽象类)
CAAnimation类的子类包括了三个:
CAAnimationGroup,CAPropertyAnimation,CATransition,
而CAPropertyAniamtion(同为抽象类)也衍生了两个子类:
CABasicAnimation和CAKeyframeAnimation。
用UIView的animation实现的动画本质上也是通过CALayer来实现的,
iOS系统中CALayer的很多属性都是隐含有动画效果的,
如果不想要隐式动画或者想要显示动画效果,也可以通过CATransaction来设置是否显示动画效果。
同时,在CATransaction内可同时修改多个属性,然后再一并同时渲染,
此外,CATransaction还是可嵌套的。
CABasicAnimation是一个最多只能有两个关键帧的动画,fromValue和toValue
而CAKeyframeAnimation除了可含有多个关键帧,而且还可以修改每个关键帧的速度。
CATransition能够为层提供移出以及移入屏幕的效果。
api只开放了其中的四种,当然你可以调用未公开的api,但是假如苹果以后出于安全还是什么原因调整接口的话,就不一定能用了,所以最好还是不要调用私有api,
例如@"flip”,@"pageCurl”这些type是属于未公开的,
一般来说不应该调用这些做动画,下面的这句
1 [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:containerView cache:YES];
同样也可实现翻转的效果。
还可以通过设置@"transform.rotation.y"这个keypath来旋转,
或者直接通过CATransform3D来设置@"transform”属性。
总而言之,没必要用未公开的api,会留下安全隐患。
变换过程中需要注意的是:
该CALayer的anchorPoint,也就是旋转缩放基准点,默认情况下是(0.5,
0.5)也就是在layer的中心。
基准点如果变了,旋转或者缩放所得到的的结果将会不同。
position和anchorPoint共同决定了CALayer在父层中的frame。
此外,CATransform3D的一个很常用到的数值,那就是m34这个值,
下面这段代码是从Xcode文档中得到的
1 CATransform3D aTransform = CATransform3DIdentity;
2 // the value of zDistance affects the sharpness of the transform.
3 zDistance = 850;
4 aTransform.m34 = 1.0 / -zDistance;
默认情况下,
m34的值为0,即zDistance为无穷大,
而(0,0,zDistance)点表示照相机(透视点)所在的位置,
从照相机位置看到的影像在xy平面上的投影即是我们可以在手机屏幕中所看到的影像,
动画的速度控制函数 CAMediaTimingFunction
这个函数是用于描述时间和距离之间的关系,
距离=toValue-fromValue,
时间=duration,
将它们标准化为1×1的空间,
x,t取值在[0, 1]区间内。
假设在时刻t(时间过去了duration*t)时,
动画目标的位置应为 fromValue+x*(toValue-fromValue)。
系统提供的四种函数,
kCAMediaTimingFunctionLinear为匀速运动,
kCAMediaTimingFunctionEaseIn为加速运动,
kCAMediaTimingFunctionEaseOut为减速运动,
kCAMediaTimingFunctionEaseInEaseOut为先加速后减速运动。
当然,系统也提供了可自定义速度控制函数,
通过initWithControlPoints:x1:y1:x2:y2可自定义两个插值点,
然后所得的函数曲线为由(0,0),(x1,y1),(x2,y2),(1,1)这四个点为控制点的Bezier曲线。
自行绘制UI,而自行绘制UI就需要用到CoreGraphics这个框架(甚至OpenGL)
CGContext类,相当于Android里面的Canvas,
使用UIGraphicsGetCurrentContext()获取当前CGContext的引用CGContextRef。
我们在每一次的绘制之前都应该保存下原来的状态,待绘制完成后再恢复回原来的状态。
CGContextSaveGState(ctx)
CGContextRestoreGState(ctx)
都应该成对的出现,在他们之间的是绘制UI的代码。
CGPath类用于描述绘制的区域或者路线。
在CGContext中addLine,addRect,其实都是在添加path,
在添加完成path以后我们可以选择是fill该path,还是stroke该path。
设置相应的颜色以及线宽即可。
如果我们只需要在某个区域中绘制,
我们可以在描述完path以后使用CGContextClip(ctx)来裁剪当前画布。
CGAffineTransform是一个仿射变换的结构体,相当于一个矩阵,
用于进行二维平面的几何变换(平移,旋转,缩放等),
而且这些几何变换都已经有封装好的函数可调用,变换的顺序就是矩阵连乘的顺序,
切不可把矩阵连乘的顺序放反,否则得到的结果可能不一样。
CGColorSpace类是用于界面颜色显示,通常颜色组成为RGBA(红,绿,蓝,透明度)四种,
使用CGColorSpaceCreateDeviceRGB获取CGColorSpace的引用CGColorSpaceRef,
需要注意的是,在CoreGraphics中,使用create方法调用release方法释放掉,如CGColorSpaceCreateDeviceRGB()对应的就是CGColorSpaceRelease(rgb)。
在用CoreGraphics绘制时,我们应该注意的是:
当使用CGContextShowTextAtPoint绘制文字时,如果不进行几何变换,得到的是文字的倒影,
所以我们需要使用CGContextSetTextMatrix (ctx, myTextTransform);
其中的transform为关于x轴对称的变换矩阵。
当然也可以不用这么做,只需要把原来CGContext的坐标先关于x轴对称,然后再平移即可,
CGContextConcatCTM(ctx, CGAffineTransformConcat(myTextTransform,
CGAffineTransformMakeTranslation(140.0f, 30.0f)));
其中的transform为关于x轴对称的变换矩阵。
同样的,使用如下语句绘制图片时,得到的也将是图片的倒影:
CGContextDrawImage(ctx, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
所以同样的也需要进行关于x轴的坐标变换。
但是使用[image drawInRect:CGRectMake(0.0f, 0.0f, image.size.width, image.size.height)];
绘制图片时就不需要关于x轴对称,默认已经是使用UIKit坐标系了。
总结,绘制文字以及图片时会得到倒影,需做一下相关的变换即可。
一.图层基本概念
Animation(动画)对CALayer来说就是:
在一段时间内,其(可动画属性)Animatable Property发生了变化.
从CALayer(CA = Core Animation)类名可知:
iOS的Layer就是为动画而生的,便于实现良好的交互体验.
一是Layer(基类CALayer),
另一是Animation(基于CAAnimation).
Animation作用于Layer.
CALayer提供了接口用于给自己添加Animation.
用于显示的Layer本质上讲是一个数据模型,
里面包含了Layer的各种属性值.
Animation则包含了动画的时间,变化,以及变化的速度函数.
二.CALayer及时间模型
UIView的职责有两个:
一个是:界面的显示
另一个是:界面事件的处理.
每一个View的背后都有一个【根层】layer
(可以通过view.layer进行访问),
本质上,layer才是用于界面显示的.
1.Layer的渲染架构
Layer也和View一样存在着一个层级树状结构,
称之为图层树(Layer Tree),
直接创建的或者通过UIView获得的(view.layer)用于显示的图层树,
被称之为模型树(Model Tree),
模型树的背后还存在两份图层树的拷贝,
一个是呈现树(Presentation Tree),
一个是渲染树(Render Tree).
呈现树可以通过普通layer(其实就是模型树)的layer.presentationLayer获得,
而模型树则可以通过modelLayer属性获得.
模型树的属性在其被修改的时候就变成了新的值,
这个是可以用代码直接操控的部分;
呈现树的属性值和动画运行过程中界面上看到的是一致的.
而渲染树是私有的,你无法访问,渲染树是对呈现树的数据进行渲染,
为了不阻塞主线程,渲染的过程是在单独的进程或线程中进行的,
所以你会发现Animation的动画并不会阻塞主线程.
2.事务管理
CALayer的那些可用于动画的(Animatable)属性,
称之为【可动画属性】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];
所有的非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()转换成秒后的值.
这个时间和系统的启动时间有关,
系统重启后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保持动画最后的状态.
其他的一些参数都是比较容易理解的.
实际应用
-(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(过渡、转场动画).
这里再强调关于动画的两个重要的点:
一是中间状态的插值计算(Interpolation),
二是动画节奏控制(TimingFunction);
有时候插值计算也和Timing有一定关系.
如果状态是一维空间的值(比如透明度),那么插值计算的结果必然再起点值和终点值之间,
如果状态是二维空间的值(比如position),
那么一般情况下插值得到的点会落在起点和终点之间的线段上(当然也有可能连线是圆滑曲线).
1.CABasicAnimation
不管是CABasicAnimation还是CAKeyframeAnimation
它们都是继承于CAPropertyAnimation.
CABasicAnimation有三个比较重要的属性:
fromValue,toValue,byValue
这三个属性都是可选的
最终都是为了确定animation变化的起点和终点.
设置了动画的起点和终点之后,
中间的值都是通过插值方式计算出来的.
插值计算的结果由timingFunction指定,
默认timingFunction为nil,即使用线性linear,也就是变化均匀.
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没有设置的时候,各个关键帧的时间是平分的.
注意:CAKeyframeAnimation中timingFunction是无效的
还可以通过设置可选参数timingFunctions为关键帧之间的过渡设置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的基础上使得动画运行变得均匀,就是系统时间内运动的距离相同,此时keyTimes以及timingFunctions也是无效的.