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:方法的矩形包含视图的全部可见区域。但在随后的调用中,该矩形只代表实际需要被描画的部分。触发视图更新的动作有如下几种:

  • 对遮挡您的视图的其它视图进行移动或删除操作。

  • 将视图的hidden属性声明设置为NO,使其从隐藏状态变为可见。

  • 将视图滚出屏幕,然后再重新回到屏幕上。

  • 显式调用视图的setNeedsDisplay或者setNeedsDisplayInRect:方法。

在调用drawRect:方法之后,视图会将自己标志为已更新,然后等待新的更新动作触发下一个更新周期。如果您的视图显示的是静态内容,则只需要在视图的可见性发生变化时进行响应就可以了,这种变化可能由滚动或其它视图是否被显示引起的。然而,如果您需要周期性地更新视图内容,就必须确定什么时候调用setNeedsDisplaysetNeedsDisplayInRect:方法来触发更新。举例来说,如果您需要每秒数次地更新内容,则可能要使用一个定时器。在响应用户交互或生成新的视图内容时,也可能需要更新视图。

坐标和坐标变换

如果您需要改变缺省的坐标系统,可以通过修改当前的转换矩阵来实现。当前转换矩阵(CTM)是一个数学矩阵,用于将视图坐标系统上的点映射到设备的屏幕上。在视图的drawRect:方法首次被调用时,就需要建立CTM,使坐标系统的原点和视图的原点互相匹配,且将坐标轴的正向分别处理为向下和向右。然而,您可以通过加入缩放、旋转、和转换因子来改变CTM,从而改变缺省坐标系统相对于潜在视图或窗口的尺寸、方向、和位置。

创建路径是开销相对较大的操作,相比之下,创建一个起始点为(0, 0)的方形,然后通过修改CTM来匹配目标描画原点的开销就少一些。

在Core Graphics框架中,有两种修改CTM的方法。您可以通过CGContext参考定义的CTM操控函数来直接修改CTM,也可以创建一个CGAffineTransform结构,将您希望的转换应用到该结构上,然后将它连结到CTM上。使用仿射变换可以将各种变换组合在一起,然后一次性地应用到CTM上。您也可以通过修改和恢复仿射变换来调整点、尺寸、和矩形的值。

图形上下文

在调用您提供的drawRect:方法之前,视图对象会自动配置其描画环境,使您的代码可以立即进行描画。作为这些配置的一部分,UIView对象会为当前描画环境创建一个图形上下文(对应于CGContextRef封装类型)。该图形上下文包含描画系统执行后续描画命令所需要的信息,定义了各种基本的描画属性,比如描画使用的颜色、裁剪区域、线的宽度及风格信息、字体信息、合成选项、以及几个其它信息。

当您希望在视图之外的其它地方进行描画时,可以创建定制的图形上下文对象。在Quartz中,当您希望捕捉一系列描画命令并将它们用于创建图像或PDF文件时,就需要这样做。您可以用CGBitmapContextCreateCGPDFContextCreate函数来创建上下文。有了上下文对象之后,您可以将它传递给创建内容时需要调用的描画函数。

您创建的定制图形上下文的坐标系统和iPhone OS使用的本地坐标系统是不同的。与后者的坐标原点位于左上角不同的是,前者的坐标原点位于左下角,其坐标值向上向右递增。您在描画命令中指定的坐标必须对此加以考虑,否则,结果图像或PDF文件在渲染时就可能会发生错误。

重要提示:由于在位图或PDF上下文中进行描画时使用的是左下原点,所以在将描画结果渲染到视图上的时候,必须对坐标系统进行补偿。换句话说,如果您创建一个图像,并调用CGContextDrawImage函数来进行描画,则该图像在缺省情况下是上下颠倒的。为了纠正这个问题,您必须将CTM的y轴进行翻转(即将该值乘以-1),使其原点从左下角移动到视图的左上角。

如果使用UIImage对象来包装您所创建的CGImageRef类型,则不需要修改CTM。UIImage对象会自动对CGImageRef 类型的坐标系统进行翻转补偿。

点和像素的不同

Quartz描画系统使用基于向量的描画模型,Quartz的描画命令则是通过固定比例的描画空间来指定,这个描画空间就是所谓的用户坐标空间

由iPhone OS将该描画空间的坐标映射为设备的实际像素。

为了维持基于向量的描画系统固有的精度,Quratz描画系统使用浮点数(而不是定点数)作为坐标值。使用浮点类型的坐标值可以非常精确地指定描画内容的位置。

->用户坐标空间是您发出的所有描画命令的工作环境。该空间的单位由点来表示。

->设备坐标空间指的是设备内在的坐标空间,由像素来表示。

颜色和颜色空间

您的描画代码应该总是使用RGB颜色。

支持的图像格式

表4-1  支持的图像格式

格式

文件扩展名

可移植网络图像格式(PNG)

.png

标记图像文件格式(TIFF)

.tiff.tif

联合影像专家组格式(JPEG)

.jpeg.jpg

图形交换格式(GIF)

.gif

视窗位图格式(DIB)

.bmp.BMPf

视窗图标格式

.ico

视窗光标

.cur

XWindow位图

.xbm

描画贴士

确定何时使用定制的描画代码

定制描画代码的使用应该限制在当显示在屏幕上的内容需要动态改变的场合。

另一方面,如果应用程序中大量的用户界面是固定的,则可以事先将那些界面渲染到一或多个图像文件中,然后在运行时通过UIImageView对象显示出来。您可以根据自己的需要,将图像视图和其它内容组合在一起。比如,您可以用UILabel对象来显示需要配置的文本,用按键或其它控件来进行交互。

提高描画的性能

表4-2  提高描画性能的贴士

Tip

Action

使描画工作最小化

在每个更新周期中,您应该只更新视图中真正发生变化的部分。如果您使用UIView的drawRect:方法来进行描画,则要通过传给该方法的更新矩形来限制描画的范围。对于基于OpenGL的描画,您必须自行跟踪更新区域。

尽可能将视图标识为不透明

合成不透明的视图所需要的开销比合成部分透明的视图要少得多。一个不透明的视图必须不包含任何透明的内容,且视图的opaque属性必须设置为YES

删除不透明的PNG文件中的alpha通道

如果一个PNG图像的每个像素都是不透明的,则将其alpha通道删除可以避免对包含该图像的图层进行融合操作,从而很大程度上简化了该图像的合成,提高描画的性能。

在滚动过程中重用表格单元和视图

应该避免在滚动过程种创建新的视图。创建新视图的开销会减少用于更新屏幕的时间,因而导致滚动不平滑。

避免在滚动过程中清除原先的内容

缺省情况下,在调用drawRect:方法对视图的某个区域进行更新之前,UIKit会清除该区域对应的上下文缓冲区。如果您对视图的滚动事件进行响应,则在滚动过程中反复清除缓冲区的开销是很大的。为了禁止这种行为,可以将clearsContextBeforeDrawing属性设置为NO

在描画过程中尽可能不改变图形状态

改变图形状态需要窗口服务器的参与。如果您要描画的内容使用类似的图形状态,则尽可能将这些内容一起描画,以减少需要改变的状态。

保持图像的质量

为用户界面提供高品质的图像应该是设计工作中的重点之一。图像是一种合理而有效的显示复杂图形的方法,任何合适的地方都可以使用。在为应用程序创建图像的时候,请记住下面的原则:

  • 使用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函数,以修改当前的图形状态。

表 4-3  修改图形状态的Core Graphics函数

图形状态

Core Graphics函数

UIKit对应组件

当前转换矩阵(CTM)

CGContextRotateCTM

CGContextScaleCTM

CGContextTranslateCTM

CGContextConcatCTM

裁剪区域

CGContextClipToRect

线: 宽度,线间链接,线端点,破折号,斜角限制

CGContextSetLineWidth

CGContextSetLineJoin

CGContextSetLineCap

CGContextSetLineDash

CGContextSetMiterLimit

曲线拟合的精度(平滑度)

CGContextSetFlatness

抗锯齿设置

CGContextSetAllowsAntialiasing

颜色:填充和笔划设置

CGContextSetRGBFillColor

CGContextSetRGBStrokeColor

UIColor

Alpha值(透明度)

CGContextSetAlpha

渲染意图

CGContextSetRenderingIntent

颜色空间:填充和笔划设置

CGContextSetFillColorSpace

CGContextSetStrokeColorSpace

文本:字体,字体尺寸,字符间隔,文本描画模式

CGContextSetFont

CGContextSetFontSize

CGContextSetCharacterSpacing

UIFont

混合模式

CGContextSetBlendMode

您可以为UIImage类和各种描画函数指定混合模式

图形上下文中包含一个保存过的图形状态堆栈。

在Quartz创建图形上下文时,该堆栈是空的。CGContextSaveGState函数的作用是将当前图形状态推入堆栈。之后,您对图形状态所做的修改会影响随后的描画操作,但不影响存储在堆栈中的拷贝。在修改完成后,您可以通过CGContextRestoreGState函数把堆栈顶部的状态弹出,返回到之前的图形状态。这种推入和弹出的方式是回到之前图形状态的快速方法,避免逐个撤消所有的状态修改;这也是将某些状态(比如裁剪路径)恢复到原有设置的唯一方式。

创建和描画图像

iPhone OS同时支持通过UIKit和Core Graphics框架装载和显示图像。到底选择哪些类和函数描画图像取决于具体的应用场合。但是,我们推荐您尽可能使用UIKit来表示图像。表4-4列举了一些使用场景及处理这些场景的推荐方法。

表 4-4  图像使用场景

场景

推荐用法

将图像作为视图的内容

使用UIImageView类装载和显示图像。这种方法假定视图的内容就是一个图像,但您仍然可以在图像视图上面放置其它视图,用于描画其它控件或内容。

将图像作为部分视图的装饰

UIImage类装载和描画图像。

将某些位图数据保存到图像对象中

使用UIGraphicsBeginImageContext函数创建一个新的、基于图像的图形上下文。在这之后,您就可以将图像内容描画在上面,然后用UIGraphicsGetImageFromCurrentImageContext函数生成一个图像(如果需要的话,您甚至可以继续描画并生成其它的图像)。在图像创建完成后,可以用UIGraphicsEndImageContext函数来关闭图形上下文。

如果您更喜欢使用Core Graphics,则可以用CGBitmapContextCreate函数创建一个位图的图形上下文,并在上面描画您的图像内容。画完之后,用CGBitmapContextCreateImage函数把位图上下文中的内容创建为一个CGImageRef类型的图像。您可以直接描画Core Graphics图像,或者用它来初始化一个UIImage

将图像保存为JPEG或PNG文件

基于原始的图像数据创建一个UIImage对象。通过UIImageJPEGRepresentationUIImagePNGRepresentation函数取得一个NSData对象,并使用该对象的方法将数据保存为文件。

下面的例子将展示如何从应用程序的程序包中装载一个图像。在该图像装载完成后,您可以将它用于初始化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中的UIRectFrameUIRectFill函数(以及其它函数)的功能是在视图中描画象矩形这样的简单路径。Core Graphics中也有一些用于创建简单路径(比如矩形和椭圆形)的便利函数。对于更为复杂的路径,必须用Core Graphics框架提供的函数自行创建。

在创建路径时,需要首先通过CGContextBeginPath函数配置一个接收路径命令的图形上下文。调用该函数之后,就可以使用与路径相关的函数来设置路径的起始点,描画直线和曲线,加入矩形和椭圆形等等。路径的几何形状指定完成后,就可以直接进行描画,或者将其引用存储在CGPathRefCGMutablePathRef数据类型中,以备后用。

在视图上描画路径时,可以描画轮廓,也可以进行填充,或者同时进行这两种操作。路径轮廓可以用像CGContextStrokePath这样的函数来画,即用当前的笔划颜色画出以路径为中心位置的线。路径的填充则可以用CGContextFillPath函数来实现,它的功能是用当前的填充颜色或样式填充路径线段包围的区域。

创建样式、渐变、和阴影

Core Graphics框架还包含一些用于创建样式、渐变、和阴影类型的函数。基于这些类型,您可以创建复杂的颜色,并用它们来填充自己创建的路径。样式是从重复出现的图像或内容创建而来的,渐变和阴影则是不同颜色之间平滑过渡的方式。

应用Core Animation的效果

关于层

Core Animation的关键技术是层对象。层是一种轻量级的对象,在本质上类似于视图,但实际上是模型对象,负责封装显示内容的几何属性、显示时机、和视觉属性变量。内容本身可以通过如下三种方式来提供:

  • 您可以将一个CGImageRef类型的数据赋值给层对象的contents属性变量

  • 您可以为层分配一个委托,让它负责描画工作。

  • 您可以从CALayer派生出子类,并对其显示方法进行重载。

当您操作层对象的属性时,您真正操作的是模型级别的数据,该数据决定了与之关联的内容应该如何被显示,而实际的渲染则由您的代码之外的模块来处理,系统对这个过程进行了大量的优化,确保渲染工作能快速完成。您需要做的只是设置层的内容和配置动画属性,然后让Core Animation接管剩下的工作。

关于动画

对于具有动画效果的层,Core Animation使用独立的动画对象来控制动画的时机和行为。CAAnimation类及其子类实现了不同类型的动画行为,供您在代码中使用。您可以创建简单的动画,将某个属性变量从一个值变为另一个值;也可以创建复杂的关键帧动画,通过您自己提供的值和时间函数来跟踪动画。

Core Animation还可以将多个动画组合为一个单独的单元,称为事务。CATransaction对象负责将一组动画组合成一个单元来管理,您也可以用它提供的方法来设置动画的持续时间。

 

posted @ 2012-02-13 18:37  Piosa  阅读(844)  评论(0编辑  收藏  举报