码迷,mamicode.com
首页 > 移动开发 > 详细

iOS_41_绘图

时间:2014-09-20 22:55:09      阅读:264      评论:0      收藏:0      [点我收藏+]

标签:ios   绘图   quartzcore2d   


bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣
bubuko.com,布布扣

Quartz 2D是一个二维图形绘制引擎,支持iOS环境和Mac OS X环境。

使用Quartz 2D API可实现许多功能,如基本路径的绘制、透明度、描影、绘制阴影、透明层、颜色管理、反锯齿、PDF文档生成和PDF元数据访问

在需要的时候,Quartz 2D还可以借助图形硬件的功能。

在Mac OS X中,Quartz 2D可以与其它图形图像技术混合使用

Core Image、Core Video、OpenGL、QuickTime。

例如,通过使用 QuickTime的GraphicsImportCreateCGImage函数,可以用 Quartz从一个 QuickTime图形导入器中创建一个图像。

Quartz 2D在图像中使用了绘画者模型(painter’s model)

绘画者模型中,每个连续的绘制操作都是将一个绘制层(a layer of ‘paint’)放置于一个画布(‘canvas’),我们通常称这个画布为(Page)。

 Page上的绘图可以通过额外的绘制操作来叠加更多的绘图。

Page上的图形对象只能通过叠加更多的绘图来改变。

Page可以是一张纸(如果输出设备是打印机),也可以是虚拟的纸张(如果输出设备是PDF文件),还可以是bitmap图像。

这根据实际使用的图形上下文graphics context而定。

绘制目标:Graphics Context【图形上下文】
Graphics Context是一个数据类型(CGContextRef),用于封装Quartz绘制图像到输出设备的信息。

设备可以是PDF文件、bitmap或者显示器的窗口上。

Graphics Context中的信息包括在Page中的图像的图形绘制参数和设备相关的表现形式。

Quartz中所有的对象都是绘制到一个【图形上下文】Graphics Context中。
可以将【图形上下文】Graphics Context想像成绘制目标,

Quartz提供了以下五种类型的【图形上下文】Graphics Context

  • 【位图】Bitmap Graphics Context
  • 【PDF】PDF Graphics Context
  • 【窗口】Window Graphics Context
  • 【图层】Layer Context
  • 【打印】Post Graphics Context

Quartz 2D 数据类型
除了 【图形上下文】Graphics Context 之外,

Quartz 2D API还定义一些数据类型。

由于这些API就Core Graphics框架的一部分,所以这些数据类型都是以CG开头的。
 
下面列出了Quartz 2D包含的数据类型:

  • CGPathRef:用于向量图(矢量),可创建路径,并进行填充或描画(stroke)
  • CGImageRef:用于表示bitmap图像和基于采样数据的bitmap图像遮罩。
  • CGLayerRef:用于表示可用于重复绘制(如背景)和幕后(offscreen)绘制的绘画层
  • CGPatternRef:用于重绘图(底纹)
  • CGShadingRef、CGGradientRef:用于绘制渐变
  • CGFunctionRef:用于定义回调函数,该函数包含一个随机的浮点值参数。当为阴影创建渐变时使用该类型
  • CGColorRef, CGColorSpaceRef:用于告诉Quartz如何使用颜色
  • CGImageSourceRef,CGImageDestinationRef:用于在Quartz中指明数据来源和目的地
  • CGFontRef:用于绘制文本时的字体
  • CGPDFDictionaryRef, CGPDFObjectRef, CGPDFPageRef, CGPDFStream, CGPDFStringRef, and CGPDFArrayRef:用于访问PDF的元数据
  • CGPDFScannerRef, CGPDFContentStreamRef:用于解析PDF元数据
  • CGPSConverterRef:用于将Mac OS中的PostScript转化成PDF。在iOS中不能使用。

当前图形状态

Quartz通过修改当前图形状态(current graphics state)来修改绘制操作的结果。

图形状态包含用于绘制程序的参数。绘制程序根据这些绘图状态来决定如何渲染结果。

例如,当你调用设置填充颜色的函数时,你将改变存储在当前绘图状态中的颜色值。
Graphics Context包含一个绘图状态栈

当Quartz创建一个【图形上下文】Graphics Context时,栈为空。

当保存图形状态时,Quartz将当前图形状态的一个副本压入栈中。

当还原图形状态时,Quartz将栈顶的图形状态出栈。

出栈的状态成为当前图形状态。
可使用函数CGContextSaveGState来保存图形状态,CGContextRestoreGState来还原图形状态。
注意:并不是当前绘制环境的所有方面都是图形状态的元素。

如,图形状态不包含当前路径(current path)。

下面列出了图形状态相关的参数:

  • Current transformation matrix (CTM):当前转换矩阵
  • Clipping area:裁剪区域
  • Line: 线
  • Accuracy of curve estimation (flatness):曲线平滑度
  • Anti-aliasing setting:反锯齿设置
  • Color: 颜色
  • Alpha value (transparency):透明度
  • Rendering intent:渲染目标
  • Color space: 颜色空间
  • Text: 文本
  • Blend mode:混合模式

Quartz 2D 坐标系统(笛卡尔坐标系)

原点在左下方,向右为X,向上为Y
坐标系统定义是被绘制到Page上的对象的位置及大小范围

 
由于不同的设备有不同的图形功能,所以图像的位置及大小依赖于设备

例如,一个显示设备可能每英寸只能显示少于96个像素,而打印机可能每英寸能显示300个像素。

如果在设备级别上定义坐标系统,则在一个设备上绘制的图形无法在其它设备上正常显示。
因此,Quartz通过使用当前转换矩阵(current transformation matrix, CTM)将一个独立的坐标系统(user space)映射到输出设备的坐标系统(device space),以此来解决设备依赖问题。 

CTM是一种特殊类型的矩阵(affine transform, 仿射矩阵),通过平移(translation)、旋转(rotation)、缩放(scale)操作将点从一个坐标空间映射到另外一个坐标空间

CTM还有另外一个目的:

允许你通过转换来决定对象如何被绘制。

例如,为了绘制一个旋转了45度的盒子,我们可以在绘制盒子之前旋转Page的坐标系统。Quartz使用旋转过的坐标系统来将盒子绘制到输出设备中。
用户空间的点用坐标对(x, y)来表示,(0, 0)表示坐标原点。Quartz中默认的坐标系统是:笛卡尔坐标系

沿着x轴从左到右坐标值逐渐增大;

沿着y轴从下到上坐标值逐渐增大。


有一些技术在设置它们的graphics context时使用了不同于Quartz的默认坐标系统。

相对于Quartz来说,这些坐标系统是修改的坐标系统(modified coordinate system),

当在这些坐标系统中显示Quartz绘制的图形时,必须进行转换。

最常见的一种修改的坐标系统是:UIKit坐标系

即:原点位于左上角,而沿着y轴向下 逐渐增大。

我们可以在如下一些地方见到这种坐标系统:

  • 在Mac OS X中,重写过isFlipped方法以返回yes的NSView类的子类
  • 在IOS中,由UIView返回的绘图上下文 
  • 在IOS中,通过调用UIGraphicsBeginImageContextWithOptions函数返回的绘图上下文 

如果应用程序想以相同的绘制程序在一个UIView对象和PDF Graphics Context上进行绘制,

需要做一个变换,以使PDF Graphics Context使用与UIView相同的坐标系。

要达到这一目的,只需要对PDF的上下文的原点做一个平移(移到左上角)、

用-1对y坐标值进行反向

如果你想要一个图片或PDF正确的绘制到一个【图形上下文】Graphics Context中,

你的应用程序可能需要临时调整Graphics Context的CTM

在IOS中,如果使用UIImage对象来创建CGImage对象,则不需要修改CTM。

UIImage将自动进行补偿以适用UIKit的坐标系统。


内存管理:对象所有权
Quartz使用Core Foundation内存管理模型(引用计数)。所以,对象的创建与销毁与通常的方式是一样的。在Quartz中,需要记住如下一些规则:

  • 如果创建create或拷贝copy一个对象,你将拥有它,因此你必须释放它。
  • 通常,如果使用含有”Create”或“Copy”单词的函数获取一个对象,
  • 当使用完后必须释放,否则将导致内存泄露。
  • 如果使用不含有”Create”或“Copy”单词的函数获取一个对象,你将不会拥有对象的引用,不需要释放它。
  • 如果你不拥有一个对象而打算保持它,则必须retain它并且在不需要时release掉。
  • 可以使用Quartz 2D的函数来指定retain和release一个对象。
  • 例如,如果创建了一个CGColorspace对象,
  • 则使用函数CGColorSpaceRetain和CGColorSpaceRelease来retain和release对象。
  • 同样,可以使用Core Foundation的CFRetain和CFRelease,但是注意不能传递NULL值给这些函数。


一、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)

bubuko.com,布布扣

在上面的:

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个单位之后,

则在投影平面上看到的点的坐标是多少呢?

bubuko.com,布布扣

bubuko.com,布布扣


上面的两个矩阵相乘则会得到最终的变换矩阵

所以一个矩阵就可以完成变换和投影

将A点坐标乘上最终的变换矩阵,

则得到 {6, 0 , -10, 1.01}, 

转换成数学坐标点为 {6/1.01, 0, 10/1.01},

则可以知道其在投影平面上的投影点为 {6/1.01, 0, 0} 

也就是我们看到的变换后的点。

其比之前较靠近原点。

越往z轴负方向移动,则在投影平面上越靠近原点。

五.几何作图解释


将上面的例子使用几何的方式来进行解释分析,

当人的眼睛 沿着y轴的正方向向下看时候,可以得到如下的景象

此时y轴垂直于屏幕指向人的眼晴

bubuko.com,布布扣

虚线为投影线,其和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的中心。

基准点如果变了,旋转或者缩放所得到的的结果将会不同。

positionanchorPoint共同决定了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(过渡、转场动画). 
bubuko.com,布布扣

这里再强调关于动画的两个重要的点:

一是中间状态的插值计算(Interpolation),

二是动画节奏控制(TimingFunction);

 有时候插值计算也和Timing有一定关系. 

如果状态是一维空间的值(比如透明度),那么插值计算的结果必然再起点值和终点值之间,

如果状态是二维空间的值(比如position),

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

1.CABasicAnimation

不管是CABasicAnimation还是CAKeyframeAnimation

它们都是继承于CAPropertyAnimation. 

bubuko.com,布布扣

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). 

bubuko.com,布布扣
自定义的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是两个控制点

bubuko.com,布布扣

如果时间变化曲线既不是直线也不是贝塞尔曲线,而是自定义的,

又或者某个图层运动的轨迹不是直线而是一个曲线,

这些是基本动画无法做到的,必须使用CAKeyframeAnimation,

也即所谓的关键帧动画.

3.CAKeyframeAnimation

任何动画要表现出运动或者变化,至少需要两个不同的关键状态,

而中间的状态的变化可以通过插值计算完成,从而形成补间动画,

表示关键状态的帧叫做关键帧. 

bubuko.com,布布扣

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也是无效的.



iOS_41_绘图

标签:ios   绘图   quartzcore2d   

原文地址:http://blog.csdn.net/pre_eminent/article/details/39430943

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!