高质量的图形是应用程序用户界面的重要组成部分。提供高质量的图形不仅会使应用程序具有好的的外观,还会使它看起来象是系统的自然扩展。iPhone OS为创建高质量的图形提供两种路径:即通过OpenGL进行渲染,或者通过Quartz、Core Animation、和UIKit进行渲染。
OpenGL框架主要适用于游戏或要求高帧率的应用程序开发。它是一组基于C语言的接口,用于在桌面电脑上创建2D和3D内容。iPhone OS通过OpenGL ES框架来支持OpenGL描画,该框架同时支持OpenGL ES 2.0和OpenGL ES v1.1。OpenGL ES是特别为嵌入式硬件系统设计的,和桌面版本的OpenGL有很多不同。
对于希望采用更为面向对象的方法进行描画的开发者,iPhone OS提供了Quartz、Core Animation、还有UIKit中的图形支持。Quartz是主要的描画接口,支持基于路径的描画、抗锯齿渲染、渐变填充模式、图像、颜色、坐标空间变换、以及PDF文档的创建、显示、和分析。UIKit为Quartz的图像和颜色操作提供了Objective-C的封装。Core Animation为很多UIKit的视图属性声明的动画效果提供底层支持,也可以用于实现定制的动画。
本章将为iPhone应用程序的描画过程提供一个概览,同时介绍描画技术的一些具体描画技巧。本章还为如何优化iPhone OS平台的描画代码提供一些指导原则和小贴士。
UIKit的图形系统
在iPhone OS上,所有的描画—无论是否采用OpenGL、Quartz、UIKit、或者Core Animation—都发生在
UIView
对象的区域内。视图定义描画发生的屏幕区域。如果您使用系统提供的视图,描画工作会自动得到处理;然而,如果您定义自己的定制视图,则必须自行提供描画代码。对于使用OpenGL进行描画的应用程序,一旦建立了渲染表面,就必须使用OpenGL指定的描画模型。
对于Quartz、Core Animation、和UIKit,您需要使用本文下面部分描述的概念。
视图描画周期
UIView
对象的基本描画模型涉及到如何按需更新视图的内容。通过收集您发出的更新请求、并在最适合的时机将它们发送给您的描画代码,
UIView
类使内容更新过程变得更为简单和高效。
任何时候,当视图的一部分需要重画时,
UIView
对象内置的描画代码就会调用其
drawRect:
方法,并向它传入一个包含需要重画的视图区域的矩形。您需要在定制视图子类中重载这个方法,并在这个方法中描画视图的内容。在首次描画视图时,UIView传递给
drawRect:
方法的矩形包含视图的全部可见区域。但在随后的调用中,该矩形只代表实际需要被描画的部分。触发视图更新的动作有如下几种:
对遮挡您的视图的其它视图进行移动或删除操作。
将视图的
hidden
属性声明设置为
NO
,使其从隐藏状态变为可见。
将视图滚出屏幕,然后再重新回到屏幕上。
显式调用视图的
setNeedsDisplay
或者
setNeedsDisplayInRect:
方法。
在调用
drawRect:
方法之后,视图会将自己标志为已更新,然后等待新的更新动作触发下一个更新周期。如果您的视图显示的是静态内容,则只需要在视图的可见性发生变化时进行响应就可以了,这种变化可能由滚动或其它视图是否被显示引起的。然而,如果您需要周期性地更新视图内容,就必须确定什么时候调用
setNeedsDisplay
或
setNeedsDisplayInRect:
方法来触发更新。举例来说,如果您需要每秒数次地更新内容,则可能要使用一个定时器。在响应用户交互或生成新的视图内容时,也可能需要更新视图。
坐标和坐标变换
如“视图坐标系统”部分描述的那样,窗口或视图的坐标原点位于左上角,坐标的值向下向右递增。当您编写描画代码时,需要通过这个坐标系统来指定描画内容中点的位置。
如果您需要改变缺省的坐标系统,可以通过修改当前的转换矩阵来实现。当前转换矩阵(CTM)是一个数学矩阵,用于将视图坐标系统上的点映射到设备的屏幕上。在视图的
drawRect:
方法首次被调用时,就需要建立CTM,使坐标系统的原点和视图的原点互相匹配,且将坐标轴的正向分别处理为向下和向右。然而,您可以通过加入缩放、旋转、和转换因子来改变CTM,从而改变缺省坐标系统相对于潜在视图或窗口的尺寸、方向、和位置。
修改CTM是在视图内容描画的标准技术,因为它需要的工作比其它方法少得多。如果您希望在当前描画系统中坐标为(20, 20)的位置上画出一个10 x 10的方形,可以首先创建一个路径,将它的起始点移动到坐标为(20, 20)的位置上,然后再画出组成方形的几条线。然而,如果您在之后希望将方形移动到坐标为(10, 10)的位置上,就必须用新的起始点重新创建路径。事实上,每次改变原点,您都必须重新创建路径。创建路径是开销相对较大的操作,相比之下,创建一个起始点为(0, 0)的方形,然后通过修改CTM来匹配目标描画原点的开销就少一些。
在Core Graphics框架中,有两种修改CTM的方法。您可以通过CGContext参考定义的CTM操控函数来直接修改CTM,也可以创建一个
CGAffineTransform
结构,将您希望的转换应用到该结构上,然后将它连结到CTM上。使用仿射变换可以将各种变换组合在一起,然后一次性地应用到CTM上。您也可以通过修改和恢复仿射变换来调整点、尺寸、和矩形的值。有关仿射变换的更多信息,请参见Quartz 2D编程指南和CGAffineTransform参考。
图形上下文
在调用您提供的
drawRect:
方法之前,视图对象会自动配置其描画环境,使您的代码可以立即进行描画。作为这些配置的一部分,
UIView
对象会为当前描画环境创建一个图形上下文(对应于
CGContextRef
封装类型)。该图形上下文包含描画系统执行后续描画命令所需要的信息,定义了各种基本的描画属性,比如描画使用的颜色、裁剪区域、线的宽度及风格信息、字体信息、合成选项、以及几个其它信息。
当您希望在视图之外的其它地方进行描画时,可以创建定制的图形上下文对象。在Quartz中,当您希望捕捉一系列描画命令并将它们用于创建图像或PDF文件时,就需要这样做。您可以用
CGBitmapContextCreate
或
CGPDFContextCreate
函数来创建上下文。有了上下文对象之后,您可以将它传递给创建内容时需要调用的描画函数。
您创建的定制图形上下文的坐标系统和iPhone OS使用的本地坐标系统是不同的。与后者的坐标原点位于左上角不同的是,前者的坐标原点位于左下角,其坐标值向上向右递增。您在描画命令中指定的坐标必须对此加以考虑,否则,结果图像或PDF文件在渲染时就可能会发生错误。
重要提示:由于在位图或PDF上下文中进行描画时使用的是左下原点,所以在将描画结果渲染到视图上的时候,必须对坐标系统进行补偿。换句话说,如果您创建一个图像,并调用
CGContextDrawImage
函数来进行描画,则该图像在缺省情况下是上下颠倒的。为了纠正这个问题,您必须将CTM的y轴进行翻转(即将该值乘以
-1
),使其原点从左下角移动到视图的左上角。
如果使用
UIImage
对象来包装您所创建的
CGImageRef
类型,则不需要修改CTM。
UIImage
对象会自动对
CGImageRef
类型的坐标系统进行翻转补偿。
有关图形上下文、如何修改图形状态信息、以及如何用图形上下文来创建定制内容的更多信息,请参见Quartz 2D编程指南。如果需要与图形上下文结合使用的函数列表,则请参见CGContext参考、CGBitmapContext参考、以及CGPDFContext参考。
点和像素的不同
Quartz描画系统使用基于向量的描画模型,这不同于基于栅格的描画模型。在栅格描画模型中,描画命令操作的是每个独立的像素,而Quartz的描画命令则是通过固定比例的描画空间来指定,这个描画空间就是所谓的用户坐标空间。然后,由iPhone OS将该描画空间的坐标映射为设备的实际像素。这个模型的优势在于,使用向量命令描画的图形在通过仿射变换放大或缩小之后仍然显示良好。
为了维持基于向量的描画系统固有的精度,Quratz描画系统使用浮点数(而不是定点数)作为坐标值。使用浮点类型的坐标值可以非常精确地指定描画内容的位置。在大多数情况下,您不必担心这些值最终如何映射到设备的屏幕。
用户坐标空间是您发出的所有描画命令的工作环境。该空间的单位由点来表示。设备坐标空间指的是设备内在的坐标空间,由像素来表示。缺省情况下,用户坐标空间上的一个点等于设备坐标空间的一个像素,这意味着一个点等于1/160英寸。然而,您不应该假定这个比例总是1:1。
颜色和颜色空间
iPhone OS支持Quartz中具有的所有颜色空间,但是,大多数应用程序应该只需要RGB颜色空间,因为iPhone OS是为嵌入式硬件设计的,而且只在一个屏幕上显示,在这种场合下,RGB颜色空间是最合适的。
UIColor
对象提供了一些便利方法,用于通过RGB、HSB、和灰度值指定颜色值。以这种方式创建颜色不需要指定颜色空间,
UIColor
对象会自动为您指定。
您也可以使用Core Graphics框架中的
CGContextSetRGBStrokeColor
和
CGContextSetRGBFillColor
函数来创建和设置颜色。虽然Core Graphics框架支持用其它的颜色空间来创建颜色,还支持创建定制的颜色空间,但是我们不推荐在描画代码中使用那些颜色。您的描画代码应该总是使用RGB颜色。
支持的图像格式
表4-1列出了iPhone OS直接支持的图像格式。在这些格式中,我们优先推荐PNG格式。
格式 |
文件扩展名 |
---|---|
可移植网络图像格式(PNG) |
|
标记图像文件格式(TIFF) |
|
联合影像专家组格式(JPEG) |
|
图形交换格式(GIF) |
|
视窗位图格式(DIB) |
|
视窗图标格式 |
|
视窗光标 |
|
XWindow位图 |
|
描画贴士
本文的下面部分将为您提供一些贴士,讨论如何在编写高质量描画代码的同时确保应用程序外观对最终用户具有吸引力。
确定何时使用定制的描画代码
根据您创建的应用程序类型,不使用或使用很少的定制代码进行描画是可能的。虽然沉浸式的应用程序通常广泛使用定制的描画代码,但是工具型和效率型的应用程序则可以使用标准的视图和控件来显示内容。
定制描画代码的使用应该限制在当显示在屏幕上的内容需要动态改变的场合。比如,用于跟踪用户描画命令的应用程序需要使用定制描画代码;还比如,游戏程序也需要经常更新屏幕,以反映游戏环境的改变。在那些情况下,您需要选择合适的描画技术,以及创建定制的视图类来正确处理事件和更新屏幕。
另一方面,如果应用程序中大量的用户界面是固定的,则可以事先将那些界面渲染到一或多个图像文件中,然后在运行时通过
UIImageView
对象显示出来。您可以根据自己的需要,将图像视图和其它内容组合在一起。比如,您可以用
UILabel
对象来显示需要配置的文本,用按键或其它控件来进行交互。
提高描画的性能
在任何平台上,描画的开销都比较昂贵,对描画代码进行优化一直都是开发过程的重要步骤。表4-2列举了几个贴士,用于确保您的描画代码得到尽可能的优化。除了这些贴士,您还应该用现有的性能工具对代码进行测试,消除描画热点和多余的描画操作。
Tip |
Action |
---|---|
使描画工作最小化 |
在每个更新周期中,您应该只更新视图中真正发生变化的部分。如果您使用UIView的 |
尽可能将视图标识为不透明 |
合成不透明的视图所需要的开销比合成部分透明的视图要少得多。一个不透明的视图必须不包含任何透明的内容,且视图的 |
删除不透明的PNG文件中的alpha通道 |
如果一个PNG图像的每个像素都是不透明的,则将其alpha通道删除可以避免对包含该图像的图层进行融合操作,从而很大程度上简化了该图像的合成,提高描画的性能。 |
在滚动过程中重用表格单元和视图 |
应该避免在滚动过程种创建新的视图。创建新视图的开销会减少用于更新屏幕的时间,因而导致滚动不平滑。 |
避免在滚动过程中清除原先的内容 |
缺省情况下,在调用 |
在描画过程中尽可能不改变图形状态 |
改变图形状态需要窗口服务器的参与。如果您要描画的内容使用类似的图形状态,则尽可能将这些内容一起描画,以减少需要改变的状态。 |
保持图像的质量
为用户界面提供高品质的图像应该是设计工作中的重点之一。图像是一种合理而有效的显示复杂图形的方法,任何合适的地方都可以使用。在为应用程序创建图像的时候,请记住下面的原则:
使用PNG格式的图像。PNG格式可以提供高品质的图像内容,是iPhone OS系统上推荐的图像格式。另外,iPhone OS对PNG图像的描画路径是经过优化的,通常比其它格式具有更高的效率。
创建大小合适的图像,避免在显示时调整尺寸。如果您计划使用特定尺寸的图像,则在创建图像资源时,务必使用相同的尺寸。不要创建一个大的图像,然后再缩小,因为缩放需要额外的CPU开销,而且需要进行插值。如果您需要以不同的尺寸显示图像,则请包含多个版本的图像,并选择与目标尺寸相对接近的图像来进行缩放。
用Quartz和UIKit进行描画
Quartz是iPhone OS的窗口服务器和描画技术的一般叫法。Core Graphics框架是Quartz的核心,也是内容描画的基本接口。该框架提供的数据类型和函数用于操作如下对象:
图形上下文
路径
图像和位图
透明层
颜色、图案颜色、和颜色空间
渐变和阴影
字体
PDF内容
UIKit在Quartz基本特性的基础上提供了一组专门的类,用于与图形相关的操作。UIKit的图形类并不是为了向您提供一个全面的描画工具箱—因为这样的工具在Core Graphics框架中已经有了,而是为了向其它UIKit类提供描画支持。UIKit包括下面的类和函数:
UIImage
, 一个不可变类,用于图像显示。
UIColor
, 为设备颜色提供基本的支持。
UIFont
, 为需要字体的类提供字体信息。
UIScreen
, 提供屏幕的基本信息。
生成
UIImage
对象的JPEG或PNG表示的函数。
描画矩形和对描画区域进行裁剪的函数。
改变和获取当前图形上下文的函数
有关UIKit包含的类和方法的信息,请参见UIKit框架参考,有关组成Core Graphics框架的封装类型和函数,请参见Core Graphics框架参考。
配置图形上下文
在您的
drawRect:
方法被调用时,视图对象的内置描画代码已经为您创建并配置好了一个缺省图形上下文。您可以通过调用
UIGraphicsGetCurrentContext
函数来取得当前上下文的指针,该函数返回一个类型为
CGContextRef
的引用,您可以将它传给Core Graphics函数,以修改当前的图形状态。表4-3列出了负责设置各种图形状态的一些主要函数,如果需要完整的函数列表,请参见CGContext参考。该表还列出了UIKit中和这些函数对应的组件,如果有的话。
图形状态 |
Core Graphics函数 |
UIKit对应组件 |
---|---|---|
当前转换矩阵(CTM) |
无 |
|
裁剪区域 |
无 |
|
线: 宽度,线间链接,线端点,破折号,斜角限制 |
无 |
|
曲线拟合的精度(平滑度) |
无 |
|
抗锯齿设置 |
无 |
|
颜色:填充和笔划设置 |
||
Alpha值(透明度) |
无 |
|
渲染意图 |
无 |
|
颜色空间:填充和笔划设置 |
无 |
|
文本:字体,字体尺寸,字符间隔,文本描画模式 |
||
混合模式 |
您可以为 |
图形上下文中包含一个保存过的图形状态堆栈。在Quartz创建图形上下文时,该堆栈是空的。
CGContextSaveGState
函数的作用是将当前图形状态推入堆栈。之后,您对图形状态所做的修改会影响随后的描画操作,但不影响存储在堆栈中的拷贝。在修改完成后,您可以通过
CGContextRestoreGState
函数把堆栈顶部的状态弹出,返回到之前的图形状态。这种推入和弹出的方式是回到之前图形状态的快速方法,避免逐个撤消所有的状态修改;这也是将某些状态(比如裁剪路径)恢复到原有设置的唯一方式。
有关图形上下文及如何用它来配置描画环境的一般信息,请参见Quartz 2D编程指南的图形上下文部分。
创建和描画图像
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
函数来实现,它的功能是用当前的填充颜色或样式填充路径线段包围的区域。
有关如何描画路径的更多信息,包括如何为复杂路径元素指定点的信息,请参见Quartz 2D编程指南的路径部分。有关路径创建函数的信息,则请参见CGContext参考和CGPath参考。
创建样式、渐变、和阴影
Core Graphics框架还包含一些用于创建样式、渐变、和阴影类型的函数。基于这些类型,您可以创建复杂的颜色,并用它们来填充自己创建的路径。样式是从重复出现的图像或内容创建而来的,渐变和阴影则是不同颜色之间平滑过渡的方式。
有关创建样式、渐变、和阴影的详细信息,在Quartz 2D编程指南中进行讨论。
用OpenGL ES进行描画
开放图形库(Open Graphics Library,即OpenGL)是一个跨平台的、基于C语言的接口,用于在桌面系统中创建2D和3D内容。游戏或需要以高帧率进行描画的开发者通常需要使用这个接口。您可以用OpenGL函数来指定图元结构,比如点、线、多边形和纹理,以及增强这些结构外观的特殊效果。您调用的函数会将图形命令发送给底层的硬件,然后由硬件进行渲染。由于大多数渲染工作是由硬件来完成,所以OpenGL的描画速度通常很快。
OpenGL的嵌入式系统版本是OpenGL的精简版本,是专门为移动设备设计的,可以充分利用现代图形硬件的优势。如果您希望为基于iPhone OS的设备—也就是iPhone或iPod Touch—创建OpenGL内容,就要使用OpenGL ES。iPhone OS系统提供的OpenGL ES框架(
OpenGLES.framework
)同时支持OpenGL ES v1.1和OpenGL ES v2.0规范。
有关iPhone OS系统上的OpenGL ES的更多信息,请参见iPhone OpenGL ES编程指南.
应用Core Animation的效果
Core Animation是一个Objective-C语言的框架,其目的是为快速创建实时动画提供基础设施。Core Animation本身并不是一个描画技术,因为它并不提供创建形状、图像、或其它内容的基本例程;相反,它是一种操作和显示由其它技术创建的内容的技术。
在iPhone OS上,大多数程序都会以某种形式受益于Core Animation技术。动画可以将当前正在发生的事情呈现给用户。比如,在用户使用Settings程序时,屏幕会根据用户是向预置的更深层次移动还是返回根结点而滑入或滑出视图。这种反馈是很重要的,可以为用户提供上下文的信息。动画还可以增强应用程序的视觉效果。
大多数情况下,您通过很少的工作就可以得到Core Animation的好处。举例来说,您可以对
UIView
类的几个属性声明(其中包括视图的边框、中心、颜色、和透明度等)进行配置,使得当它们的值发生变化时,可以触发动画效果。您需要通过少量的工作让UIKit知道您希望执行哪些动画,但动画的创建和运行都是自动的。有关如何触发内置视图动画的更多信息,请参见“视图动画”部分。
如果您要超越基本的动画效果,就必须直接和Core Animation的类及方法进行更多的交互。本文的下面部分将进一步提供有关Core Animation的信息,向您展示如何用它提供的类和方法创建iPhone OS上的典型动画。更多有关Core Animation及其用法的信息,请参见Core Animation编程指南。
关于层
Core Animation的关键技术是层对象。层是一种轻量级的对象,在本质上类似于视图,但实际上是模型对象,负责封装显示内容的几何属性、显示时机、和视觉属性变量。内容本身可以通过如下三种方式来提供:
您可以将一个
CGImageRef
类型的数据赋值给层对象的
contents
属性变量。
您可以为层分配一个委托,让它负责描画工作。
您可以从
CALayer
派生出子类,并对其显示方法进行重载。
当您操作层对象的属性时,您真正操作的是模型级别的数据,该数据决定了与之关联的内容应该如何被显示,而实际的渲染则由您的代码之外的模块来处理,系统对这个过程进行了大量的优化,确保渲染工作能快速完成。您需要做的只是设置层的内容和配置动画属性,然后让Core Animation接管剩下的工作。
更多有关层及如何使用层的信息,请参见Core Animation编程指南。
关于动画
对于具有动画效果的层,Core Animation使用独立的动画对象来控制动画的时机和行为。
CAAnimation
类及其子类实现了不同类型的动画行为,供您在代码中使用。您可以创建简单的动画,将某个属性变量从一个值变为另一个值;也可以创建复杂的关键帧动画,通过您自己提供的值和时间函数来跟踪动画。
Core Animation还可以将多个动画组合为一个单独的单元,称为事务。
CATransaction
对象负责将一组动画组合成一个单元来管理,您也可以用它提供的方法来设置动画的持续时间。
如果您需要如何创建定制动画的实例,请参见动画类型和时机的编程指南。
下页上页