iPhone应用程序编程指南(图形和描画)
iPhone OS为创建高质量的图形提供两种路径:即通过OpenGL进行渲染,或者通过Quartz、Core Animation、和UIKit进行渲染。
UIKit的图形系统
在iPhone OS上,所有的描画—无论是否采用OpenGL、Quartz、UIKit、或者Core Animation—都发生在UIView
对象的区域内。视图定义描画发生的屏幕区域。如果您使用系统提供的视图,描画工作会自动得到处理;然而,如果您定义自己的定制视图,则必须自行提供描画代码。对于使用OpenGL进行描画的应用程序,一旦建立了渲染表面,就必须使用OpenGL指定的描画模型。
视图描画周期
UIView
对象的基本描画模型涉及到如何按需更新视图的内容。通过收集您发出的更新请求、并在最适合的时机将它们发送给您的描画代码,UIView
类使内容更新过程变得更为简单和高效。
任何时候,当视图的一部分需要重画时,UIView
对象内置的描画代码就会调用其drawRect:
方法,并向它传入一个包含需要重画的视图区域的矩形。您需要在定制视图子类中重载这个方法,并在这个方法中描画视图的内容。在首次描画视图时,UIView传递给drawRect:
方法的矩形包含视图的全部可见区域。但在随后的调用中,该矩形只代表实际需要被描画的部分。触发视图更新的动作有如下几种:
-
对遮挡您的视图的其它视图进行移动或删除操作。
-
将视图滚出屏幕,然后再重新回到屏幕上。
-
显式调用视图的
setNeedsDisplay
或者setNeedsDisplayInRect:
方法。
在调用drawRect:
方法之后,视图会将自己标志为已更新,然后等待新的更新动作触发下一个更新周期。如果您的视图显示的是静态内容,则只需要在视图的可见性发生变化时进行响应就可以了,这种变化可能由滚动或其它视图是否被显示引起的。然而,如果您需要周期性地更新视图内容,就必须确定什么时候调用setNeedsDisplay
或setNeedsDisplayInRect:
方法来触发更新。举例来说,如果您需要每秒数次地更新内容,则可能要使用一个定时器。在响应用户交互或生成新的视图内容时,也可能需要更新视图。
坐标和坐标变换
如果您需要改变缺省的坐标系统,可以通过修改当前的转换矩阵来实现。当前转换矩阵(CTM)是一个数学矩阵,用于将视图坐标系统上的点映射到设备的屏幕上。在视图的drawRect:
方法首次被调用时,就需要建立CTM,使坐标系统的原点和视图的原点互相匹配,且将坐标轴的正向分别处理为向下和向右。然而,您可以通过加入缩放、旋转、和转换因子来改变CTM,从而改变缺省坐标系统相对于潜在视图或窗口的尺寸、方向、和位置。
创建路径是开销相对较大的操作,相比之下,创建一个起始点为(0, 0)的方形,然后通过修改CTM来匹配目标描画原点的开销就少一些。
在Core Graphics框架中,有两种修改CTM的方法。您可以通过CGContext参考定义的CTM操控函数来直接修改CTM,也可以创建一个CGAffineTransform
结构,将您希望的转换应用到该结构上,然后将它连结到CTM上。使用仿射变换可以将各种变换组合在一起,然后一次性地应用到CTM上。您也可以通过修改和恢复仿射变换来调整点、尺寸、和矩形的值。
图形上下文
在调用您提供的drawRect:
方法之前,视图对象会自动配置其描画环境,使您的代码可以立即进行描画。作为这些配置的一部分,UIView
对象会为当前描画环境创建一个图形上下文(对应于CGContextRef
封装类型)。该图形上下文包含描画系统执行后续描画命令所需要的信息,定义了各种基本的描画属性,比如描画使用的颜色、裁剪区域、线的宽度及风格信息、字体信息、合成选项、以及几个其它信息。
当您希望在视图之外的其它地方进行描画时,可以创建定制的图形上下文对象。在Quartz中,当您希望捕捉一系列描画命令并将它们用于创建图像或PDF文件时,就需要这样做。您可以用CGBitmapContextCreate
或CGPDFContextCreate
函数来创建上下文。有了上下文对象之后,您可以将它传递给创建内容时需要调用的描画函数。
您创建的定制图形上下文的坐标系统和iPhone OS使用的本地坐标系统是不同的。与后者的坐标原点位于左上角不同的是,前者的坐标原点位于左下角,其坐标值向上向右递增。您在描画命令中指定的坐标必须对此加以考虑,否则,结果图像或PDF文件在渲染时就可能会发生错误。
重要提示:由于在位图或PDF上下文中进行描画时使用的是左下原点,所以在将描画结果渲染到视图上的时候,必须对坐标系统进行补偿。换句话说,如果您创建一个图像,并调用CGContextDrawImage
函数来进行描画,则该图像在缺省情况下是上下颠倒的。为了纠正这个问题,您必须将CTM的y轴进行翻转(即将该值乘以-1
),使其原点从左下角移动到视图的左上角。
如果使用UIImage
对象来包装您所创建的CGImageRef
类型,则不需要修改CTM。UIImage
对象会自动对CGImageRef
类型的坐标系统进行翻转补偿。
点和像素的不同
Quartz描画系统使用基于向量的描画模型,Quartz的描画命令则是通过固定比例的描画空间来指定,这个描画空间就是所谓的用户坐标空间。
由iPhone OS将该描画空间的坐标映射为设备的实际像素。
为了维持基于向量的描画系统固有的精度,Quratz描画系统使用浮点数(而不是定点数)作为坐标值。使用浮点类型的坐标值可以非常精确地指定描画内容的位置。
->用户坐标空间是您发出的所有描画命令的工作环境。该空间的单位由点来表示。
->设备坐标空间指的是设备内在的坐标空间,由像素来表示。
颜色和颜色空间
您的描画代码应该总是使用RGB颜色。
支持的图像格式
格式 |
文件扩展名 |
---|---|
可移植网络图像格式(PNG) |
|
标记图像文件格式(TIFF) |
|
联合影像专家组格式(JPEG) |
|
图形交换格式(GIF) |
|
视窗位图格式(DIB) |
|
视窗图标格式 |
|
视窗光标 |
|
XWindow位图 |
|
描画贴士
确定何时使用定制的描画代码
定制描画代码的使用应该限制在当显示在屏幕上的内容需要动态改变的场合。
另一方面,如果应用程序中大量的用户界面是固定的,则可以事先将那些界面渲染到一或多个图像文件中,然后在运行时通过UIImageView
对象显示出来。您可以根据自己的需要,将图像视图和其它内容组合在一起。比如,您可以用UILabel
对象来显示需要配置的文本,用按键或其它控件来进行交互。
提高描画的性能
Tip |
Action |
---|---|
使描画工作最小化 |
在每个更新周期中,您应该只更新视图中真正发生变化的部分。如果您使用UIView的 |
尽可能将视图标识为不透明 |
合成不透明的视图所需要的开销比合成部分透明的视图要少得多。一个不透明的视图必须不包含任何透明的内容,且视图的 |
删除不透明的PNG文件中的alpha通道 |
如果一个PNG图像的每个像素都是不透明的,则将其alpha通道删除可以避免对包含该图像的图层进行融合操作,从而很大程度上简化了该图像的合成,提高描画的性能。 |
在滚动过程中重用表格单元和视图 |
应该避免在滚动过程种创建新的视图。创建新视图的开销会减少用于更新屏幕的时间,因而导致滚动不平滑。 |
避免在滚动过程中清除原先的内容 |
缺省情况下,在调用 |
在描画过程中尽可能不改变图形状态 |
改变图形状态需要窗口服务器的参与。如果您要描画的内容使用类似的图形状态,则尽可能将这些内容一起描画,以减少需要改变的状态。 |
保持图像的质量
为用户界面提供高品质的图像应该是设计工作中的重点之一。图像是一种合理而有效的显示复杂图形的方法,任何合适的地方都可以使用。在为应用程序创建图像的时候,请记住下面的原则:
-
使用PNG格式的图像。
-
创建大小合适的图像,避免在显示时调整尺寸。
用Quartz和UIKit进行描画
Quartz是iPhone OS的窗口服务器和描画技术的一般叫法。Core Graphics框架是Quartz的核心,也是内容描画的基本接口。
UIKit在Quartz基本特性的基础上提供了一组专门的类,用于与图形相关的操作。UIKit的图形类并不是为了向您提供一个全面的描画工具箱—因为这样的工具在Core Graphics框架中已经有了,而是为了向其它UIKit类提供描画支持。UIKit包括下面的类和函数:
-
UIImage
, 一个不可变类,用于图像显示。 -
UIColor
, 为设备颜色提供基本的支持。 -
UIFont
, 为需要字体的类提供字体信息。 -
UIScreen
, 提供屏幕的基本信息。 -
生成
UIImage
对象的JPEG或PNG表示的函数。 -
描画矩形和对描画区域进行裁剪的函数。
-
改变和获取当前图形上下文的函数
配置图形上下文
在您的drawRect:
方法被调用时,视图对象的内置描画代码已经为您创建并配置好了一个缺省图形上下文。您可以通过调用UIGraphicsGetCurrentContext
函数来取得当前上下文的指针,该函数返回一个类型为CGContextRef
的引用,您可以将它传给Core Graphics函数,以修改当前的图形状态。
图形状态 |
Core Graphics函数 |
UIKit对应组件 |
---|---|---|
当前转换矩阵(CTM) |
无 |
|
裁剪区域 |
无 |
|
线: 宽度,线间链接,线端点,破折号,斜角限制 |
无 |
|
曲线拟合的精度(平滑度) |
无 |
|
抗锯齿设置 |
无 |
|
颜色:填充和笔划设置 |
||
Alpha值(透明度) |
无 |
|
渲染意图 |
无 |
|
颜色空间:填充和笔划设置 |
无 |
|
文本:字体,字体尺寸,字符间隔,文本描画模式 |
||
混合模式 |
您可以为 |
图形上下文中包含一个保存过的图形状态堆栈。
在Quartz创建图形上下文时,该堆栈是空的。CGContextSaveGState
函数的作用是将当前图形状态推入堆栈。之后,您对图形状态所做的修改会影响随后的描画操作,但不影响存储在堆栈中的拷贝。在修改完成后,您可以通过CGContextRestoreGState
函数把堆栈顶部的状态弹出,返回到之前的图形状态。这种推入和弹出的方式是回到之前图形状态的快速方法,避免逐个撤消所有的状态修改;这也是将某些状态(比如裁剪路径)恢复到原有设置的唯一方式。
创建和描画图像
iPhone OS同时支持通过UIKit和Core Graphics框架装载和显示图像。到底选择哪些类和函数描画图像取决于具体的应用场合。但是,我们推荐您尽可能使用UIKit来表示图像。表4-4列举了一些使用场景及处理这些场景的推荐方法。
场景 |
推荐用法 |
---|---|
将图像作为视图的内容 |
使用 |
将图像作为部分视图的装饰 |
用 |
将某些位图数据保存到图像对象中 |
使用 如果您更喜欢使用Core Graphics,则可以用 |
将图像保存为JPEG或PNG文件 |
基于原始的图像数据创建一个 |
下面的例子将展示如何从应用程序的程序包中装载一个图像。在该图像装载完成后,您可以将它用于初始化UIImageView
对象、将它保存到磁盘、或者在视图的drawRect:
方法中进行显式描画。
NSString* imagePath = [[NSBundle mainBundle] pathForResource:@"myImage" ofType:@"png"]; |
UIImage* myImageObj = [[UIImage alloc] initWithContentsOfFile:imagePath]; |
在视图的drawRect:
方法中,您可以使用UIImage
类提供的任何描画方法。您可以指定希望在视图的什么位置描画图像,从而避免在描画之前进行位置的转换。假定您将之前装载的图像存储在一个名为
anImage
的成员变量中,下面的代码会将该图像画在视图的(10, 10) 坐标位置上:
- (void)drawRect:(CGRect)rect |
{ |
// Draw the image |
[anImage drawAtPoint:CGPointMake(10, 10)]; |
} |
重要提示:如果您使用CGContextDrawImage
函数来直接描画位图,则在缺省情况下,图像数据会上下倒置,因为Quartz图像假定坐标系统的原点在左下角,且坐标轴的正向是向上和向右。虽然您可以在描画之前对其进行转换,但是将Quartz图像包装为一个UIImage
对象是更简单的方法,这样可以自动补偿坐标空间的差别。有关如何用Core Graphics创建和描画图像的更多信息,请参见Quartz 2D编程指南。
创建和描画路径
路径用于描述由一序列线和Bézier曲线构成的2D几何形状。UIKit中的UIRectFrame
和UIRectFill
函数(以及其它函数)的功能是在视图中描画象矩形这样的简单路径。Core Graphics中也有一些用于创建简单路径(比如矩形和椭圆形)的便利函数。对于更为复杂的路径,必须用Core Graphics框架提供的函数自行创建。
在创建路径时,需要首先通过CGContextBeginPath
函数配置一个接收路径命令的图形上下文。调用该函数之后,就可以使用与路径相关的函数来设置路径的起始点,描画直线和曲线,加入矩形和椭圆形等等。路径的几何形状指定完成后,就可以直接进行描画,或者将其引用存储在CGPathRef
或CGMutablePathRef
数据类型中,以备后用。
在视图上描画路径时,可以描画轮廓,也可以进行填充,或者同时进行这两种操作。路径轮廓可以用像CGContextStrokePath
这样的函数来画,即用当前的笔划颜色画出以路径为中心位置的线。路径的填充则可以用CGContextFillPath
函数来实现,它的功能是用当前的填充颜色或样式填充路径线段包围的区域。
创建样式、渐变、和阴影
Core Graphics框架还包含一些用于创建样式、渐变、和阴影类型的函数。基于这些类型,您可以创建复杂的颜色,并用它们来填充自己创建的路径。样式是从重复出现的图像或内容创建而来的,渐变和阴影则是不同颜色之间平滑过渡的方式。
应用Core Animation的效果
关于层
Core Animation的关键技术是层对象。层是一种轻量级的对象,在本质上类似于视图,但实际上是模型对象,负责封装显示内容的几何属性、显示时机、和视觉属性变量。内容本身可以通过如下三种方式来提供:
当您操作层对象的属性时,您真正操作的是模型级别的数据,该数据决定了与之关联的内容应该如何被显示,而实际的渲染则由您的代码之外的模块来处理,系统对这个过程进行了大量的优化,确保渲染工作能快速完成。您需要做的只是设置层的内容和配置动画属性,然后让Core Animation接管剩下的工作。
关于动画
对于具有动画效果的层,Core Animation使用独立的动画对象来控制动画的时机和行为。CAAnimation
类及其子类实现了不同类型的动画行为,供您在代码中使用。您可以创建简单的动画,将某个属性变量从一个值变为另一个值;也可以创建复杂的关键帧动画,通过您自己提供的值和时间函数来跟踪动画。
Core Animation还可以将多个动画组合为一个单独的单元,称为事务。CATransaction
对象负责将一组动画组合成一个单元来管理,您也可以用它提供的方法来设置动画的持续时间。