iOS图形API简介:第1部分(译文)
By S.F.
本文链接 https://www.kyfws.com/news/introduction-to-ios-graphics-apis-part-1/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 25 分钟阅读 - 12107 个词 阅读量 0iOS图形API简介:第1部分(译文)
原文地址:https://www.codeproject.com/Articles/93563/Introduction-to-iOS-Graphics-APIs-Part-1
原文作者:Joel Ivory Johnson
译文由本站翻译
前言
iOS图形API简介的第一部分.在本文中,我将介绍一些Quartz 2D/Core Graphics API.
介绍
在本系列的第一篇文章中,我提供了对Objective-C的快速介绍,并简要介绍了内存管理,使用控件以及将信息持久保存到文件中.在本文中,我想介绍一些图形功能.由于它将提供更好的显示表面,因此我将把iPad模拟器用作本文的目标设备.但是,此处显示的API将在iPad,iPhone和iPod Touch上运行.由于这些API是从Mac OS X移植的,因此这些API也可以在Macintosh上运行.
先决条件
要利用本文,您需要熟悉Objective-C和iPhone开发.如果您不这样做,那么您将希望看看我在iPhone开发上写的第一篇文章.随着图形和数学的齐头并进,您还希望对数学(代数和某些三角函数)感到满意.本文所需的唯一硬件是运行Snow Leopard和iOS SDK的基于Intel的Macintosh.
可用的API
iPhone支持两个图形API系列:Core Graphics/Quartz 2D和OpenGL ES. OpenGL ES是跨平台的图形API. Quartz 2D是Apple专用的API.它是Core Graphics框架的一部分. OpenGL ES是更大的图形API(OpenGL)的精简版.请记住,OpenGL ES是一个应用程序编程接口.它描述了可用的功能,结构,使用方式的语义以及功能应具有的行为.设备制造商如何选择实现这些行为并符合此规范将是他们的实现.我指出这一点是因为我由于对接口和实现之间的差异的误解而遇到了很多对话.如果很难理解这种差异,请考虑一下此类比喻:发条和电子钟都具有相同的视觉界面和相同的行为,但是它们的内部工作原理(实现)不同.由于制造商可以自由实施OpenGL ES,因此您会发现不同系统之间的性能差异很大.值得庆幸的是,在iOS设备上,与现有的其他支持OpenGL ES的设备相比,性能范围的下限仍然很高.
代表颜色
有几种不同的方法可以用数字表示颜色.典型的方法是通过表达原色的强度来表达一种颜色,这些原色混合在一起会产生相同的颜色.原色是红色,绿色和蓝色.如果您认为黄色是主要颜色而不是绿色,那么您可能会想到主要减色颜色(与在纸上使用油漆有关,而与照亮像素无关). Quartz 2D还支持其他数字表示颜色的系统,但这里不再赘述.我只会使用以红色,绿色和蓝色表示的颜色.这也称为RGB颜色空间.这些颜色的每种成分均表示为浮点数.最低强度为0,最高强度为1.0. 除了这些像素强度外,还有第四个颜色分量,通常称为" Alpha". alpha组件用于表示透明度.如果颜色完全不透明(非透明),则此值为1.0.如果颜色是完全透明的(因此是不可见的),则该值将为0.当RGB颜色还具有alpha分量(取决于所查看的系统)时,这将称为ARGB色彩空间或RGBA色彩空间(是Alpha组件所在的位置).在本材料的其余部分中,将使用RGBA来描述这种类型的颜色.虽然Quartz 2D支持多种不同的颜色格式,但OpenGL ES仅支持RGBA.
屏幕坐标
在屏幕上定位项目时,通常会使用点(CGPoint
)在屏幕上定位项目.自然地假设点坐标和像素坐标相同.但是在iOS中,情况并非总是如此.点不一定映射到相同坐标的像素.映射由系统处理.当您查看一个应用程序如何在具有不同像素分辨率的设备上运行时,您会发现它发挥了最大作用.如果要查看像素和点之间的关系,可以查看由UIImage,UIScreen或UIView类公开的比例因子.
Quartz 2D和核心图形
使用Quartz 2D,您可以渲染到视图或内存图像.在其上绘制的表面具有颜色,如果使用透明颜色绘制,则如果调用各种函数以渲染到表面上,则该颜色将与绘制时的颜色混合.在示例程序中,我们将从绘制到" UIView"开始,以便您可以立即进入Quartz 2D的工作方式.为此,我们将创建一个从UIView派生的新视图类,并在对象的[(void)drawRect:(CGRect)rect](http://developer.apple.com/iphone/library/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/drawRect :)方法. 核心图形API均在上下文中起作用.您需要获取视图的上下文,并将其传递给Quartz 2D函数进行渲染.如果要渲染到内存中的图像,则应传递其上下文.可以通过以下函数调用获取视图的上下文:
CGContextRef context = UIGraphicsGetCurrentContext();
构建您的第一个Quartz 2D应用程序
打开Xcode并创建一个名为MyDrawingApp的新的基于iOS View的应用程序.创建应用程序后,单击"类"文件夹.我们将创建一个新的UIView控件,并在该视图中执行渲染.右键单击Classes文件夹并选择" Add New File",创建一个新的Cocoa Touch类文件.选择Objective-C类,然后选择" UIView"作为设置的子类. (默认值为NSObject.请确保未选中该选项.)单击"下一步",然后在提示您输入文件名称时,输入" MyDrawingView.m". * .h和* .m文件都将被创建.
对于第一个程序,我唯一要做的就是在屏幕上绘制一些东西.除了在屏幕上绘制内容外,此程序无能为力.打开刚才添加的类的* .m文件.我们将从覆盖类的初始化方法开始.我们的此类实例将在Interface Builder中创建.以这种方式创建的对象通过调用With
WithCoder:而不是调用
init`来初始化.这就是我们需要重写的方法.
-(id) initWithCoder:(NSCoder*)sourceCoder
{
if( ( self = [super initWithCoder:sourceCoder]))
{
//place any other initialization here
}
return self;
}
现在,初始化方法中我们不需要做任何事情.但是我让您将它作为其他代码的占位符包含在此处.为了在电话上显示此视图,我们将其设置为应用程序的基类.在Xcode中,找到MyDrawingAppViewController.xib并在Interface Builder中将其打开.按Command-4以确保身份检查器处于打开状态.您会看到当前视图已设置为继承自UIView.我们想让它从我们的类MyDrawingView继承.保存所做的更改,然后关闭"界面生成器".编译并运行代码以确保一切正常.完成此操作后,我们就可以开始绘制了!
在MyDrawingView.m中,有一个名为drawRect:
的方法,其中不包含任何代码.那就是我们要放置绘图代码的地方.我们需要获取图形上下文,设置绘制颜色和其他属性,然后在屏幕上绘制形状.现在,让我们画一条简单的线.
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
UIColor* currentColor = [UIColor redColor];
CGContextRef context = UIGraphicsGetCurrentContext();
//Set the width of the "pen" that will be used for drawing
CGContextSetLineWidth(context,4);
//Set the color of the pen to be used
CGContextSetStrokeColorWithColor(context, currentColor.CGColor);
//Move the pen to the upper left hand corner
CGContextMoveToPoint(context, 0, 0);
//and draw a line to position 100,100 (down and to the right)
CGContextAddLineToPoint(context, 100, 100);
//Apply our stroke settings to the line.
CGContextStrokePath(context);
[currentColor release];
}
打开MyDrawingAppViewController.xib,然后单击"查看"图标.当它突出显示时,按Command-4以确保选择了身份检查器.在Class设置旁边,将下拉菜单从UIView更改为MyDrawingView.关闭Interface Builder并保存您的更改.返回Xcode并运行您的项目.您会在屏幕的左上角看到一条红线. 虽然与图形没有直接关系,但我想尝试一下触摸交互.如果该程序是交互式的,则可能会更有趣.我们将对其进行更改,以便在屏幕上拖动手指在您选择的两个点之间绘制线条.我们还将更改程序以保留其对颜色的引用,而不是每次刷新屏幕时都获取一个新的颜色.打开MyDrawingViewView.h文件并进行以下添加:
#import <uikit/uikit.h>
@interface MyDrawingView : UIView {
CGPoint fromPoint;
CGPoint toPoint;
UIColor* currentColor;
}
@property CGPoint fromPoint;
@property CGPoint toPoint;
@property UIColor* currentColor;
@end
适当的@synthasize语句将需要添加到MyDrawingView.m文件的顶部.将以下内容添加到该文件:
#import "MyDrawingView.h"
@implementation MyDrawingView
@synthesize fromPoint;
@synthesize toPoint;
@synthesize currentColor;
到目前为止,我还没有说过任何有关触摸交互的内容.我将在另一篇文章中讨论触摸事件和其他事件处理.现在,我将采取satisfying路线并通过感兴趣的互动来加快速度.我们需要响应三个事件以将触摸交互添加到程序中. touchesBegan:
,touchesEnded:
和touchesMoved:
.所需事件的代码如下.将其添加到您的MyDrawingView.m文件.
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touchPoint = [touches anyObject];
fromPoint = [touchPoint locationInView:self];
toPoint = [touchPoint locationInView:self];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
toPoint=[touch locationInView:self];
[self setNeedsDisplay];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
toPoint = [touch locationInView:self];
[self setNeedsDisplay];
}
剩下的唯一事情就是更改绘图代码,以便与其在两个固定点之间绘图,不如在我们触摸的点之间绘图,并删除绘图代码中的" currentColor"声明和释放(因为我们现在正在使用成员属性以存储颜色).
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context,4);
CGContextSetStrokeColorWithColor(context, currentColor.CGColor);
CGContextMoveToPoint(context,fromPoint.x , fromPoint.y);
CGContextAddLineToPoint(context, toPoint.x, toPoint.y);
CGContextStrokePath(context);
}
运行该程序,然后尝试在屏幕上的各个位置上拖动手指(或鼠标).您将看到触摸的点之间的线.
处理图像
iPhone上有两张图像.它们是CGImage和UIImage. CGImage是一个结构,其中包含可以传递给各种Core Graphics函数的图像数据. UIImage
是一个Objective-C类.到目前为止," UIImage"类更易于使用,因此让我们从使用它开始在程序中绘制图像开始.在计算机上查找500x500像素以下的图像.图像可以是PNG或JPEG文件.在Xcode项目中,您将看到一个名为Resources的文件夹.单击并将图像拖到Xcode的"资源"文件夹中,然后在出现提示时,选择"将项目复制到目标组的文件夹中"的选项(如果需要).我正在使用一个名为office.jpg的文件,并将以此名称引用我的图像文件.请记住将其替换为图像的名称.
在MyDrawingView.h文件中,声明一个名为backgroundImage
的新UIImage *变量.在MyDrawingView.m实现文件中,添加一个@syntasize backgrounImage;语句.初始化视图时,我们需要从资源中加载图像.在-(id)initWithCoder:
方法中,添加backgroundImage
=[UIImage imageNamed:@" office.jpg"
];.切记将" @" office.jpg"替换为图像文件的名称.此行将从资源中加载图像.在-(void)drawRect:
方法的顶部,添加以下两行:
CGPoint drawingTargetPoint = CGPointMake(0,0);
[backgroundImage drawAtPoint:drawingTargetPoint];
如果您现在运行该程序,它将在您绘制的线条后面显示一个背景图像.
点数与像素
iOS设备屏幕的物理分辨率和用于绘制的坐标之间存在概念上的隔离层.在许多图形环境中,术语"点"和"像素"可以互换使用.在iOS设备上,操作系统会将点映射到像素.在位置(10,25)绘制物体可能会或可能不会导致对象出现在左侧10像素和顶部25像素的位置.可以通过比例因子查询点与实际像素之间的关系,该比例因子可以从" UIScreen"," UIView"或" UIImage"中读取.当您查看在iPhone 3G和iPhone 4上运行的同一程序时,可以看到逻辑坐标与物理坐标分离的结果.假定开发人员没有做任何事情来利用iPhone 4屏幕的更高分辨率,当代码在设备的屏幕上绘制线条或图像时,它将在设备的屏幕上占据相同比例的空间. 基于矢量的操作(例如绘制矩形,直线和其他几何形状)可以在标准和更高分辨率的设备上正常运行,而无需调整代码.对于位图,您需要做一些额外的工作.您将需要图像的标准和高分辨率版本,以获得最佳效果.资源的名称应符合特定的模式.有一种用于标准分辨率设备的模式,另一种用于高分辨率设备的模式. 资源名称的[DeviceModifier]部分是可选的.它可以是字符串〜iphone或〜ipad.图像的低分辨率和高分辨率版本的名称之间的主要区别是名称中的" @ 2".高分辨率图像的宽度和高度应该是标准分辨率图像的宽度和高度的两倍. (对于任何熟悉MIP地图的人来说,都是熟悉的.)
路径
路径描述形状.路径可以由直线,矩形,椭圆和其他形状组成.使用点指定绘图空间内的坐标.容易将点视为像素,但它们不是同一件事(Points vs. Pixels部分中有更多内容).通常,您将通过传递一对浮点数或使用CGPoint
结构来通信点.您已经在上面构建的程序中看到CGContextAddLineToPoint
.还有一个CGContextAddLines函数,用于绘制多条线,这些线的点在数组中传递. CGContextAddEllipseInRect添加椭圆.这两个函数都接受一个" CGRect",它定义了限制要绘制形状的矩形.
可以使用函数CGContextAddCurveToPoint生成曲线(更具体地讲,贝塞尔曲线).曲线将从最后一次绘制操作的点开始(请记住,您可以使用CGMovePointToDraw更改此点),并在函数调用中指定的点结束,并且曲线将受到两个控制点的影响也将在函数调用中传递.如果您以前从未使用过Bezier曲线,请在[Wikipedia.org](http://en.wikipedia.org/wiki/BÃzier_curve)上找到一篇不错的文章. 如果需要创建一个复杂的路径(一个由许多路径组成的路径),可以先调用CGContextBeginPath,然后通过调用CGContextMoveToPoint来设置路径的起点.然后打电话给路径添加形状.完成后,使用
CGContextClosePath关闭路径.创建路径不会将其呈现到屏幕上.除非您对其进行绘制,否则不会渲染它.绘制完毕后,该路径将从图形上下文中删除,您可以开始渲染新路径(或其他操作). 要绘制路径,请应用" CGContextStrokePath"或" CGContextFillPath"应用笔触和/或对其进行填充.笔触会影响路径周围的线条的显示方式(也称为边框).在其他函数中,使用函数CGContextSetLineWidth和CGContextSetStrokeColor或CGContextSetStrokeColorWithColor设置线条的颜色.调用
CGStrokePath`会将描边应用于当前路径.
简单几何的填充规则很简单,不需要太多解释.线内的区域被填充.创建边界重叠的自定义路径时,要填充的区域的规则要复杂一些.根据Apple文档,使用的规则称为非零缠绕数规则(请在[此处]找到(http://developer.apple.com/iphone/library/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_paths/dq_paths.html#//apple_ref/doc/uid/TP30001066-CH211-TPXREF101).用来确定某个点是否在要填充区域内的过程有点抽象.选择要测试的点,然后从该点画一条线到图形的边界之外,计算其相交的路径段的数量.从零开始,每次线与从左至右的路径段相交时,将其加到计数中,而每当与从右至左的路径段相交时,将其减去.如果结果是奇数,则应填充该点.如果结果是偶数,则不应填充该点.另一种规则是简单地计算在上述过程中绘制的线与路径段相交的次数,而与该段的方向无关.如果结果是偶数,则不要填补重点.否则,该点将被填补.这称为奇数规则.
剪裁
上下文会自动具有一个与其表面大小相同的裁剪表面.如果需要进一步限制发生绘图的区域,则可以创建其他表面积.要创建新的剪切区域,请创建路径,然后调用剪切函数而不是绘图函数.最终的裁剪区域是当前裁剪区域和所应用的裁剪区域的交集.裁剪被认为是图形状态的一部分.如果需要设置和还原剪切区域,则需要保存然后还原上下文. CGContextClip将当前路径应用于当前剪切区域. CGContextClip`ToRect将一个矩形应用于剪贴区域. CGContectClipToRects会将多个矩形应用于剪贴区域.
渐变色
渐变是逐渐改变颜色的区域. Quartz 2D提供两种类型的渐变:线性(或轴向)渐变和径向渐变.渐变颜色的更改还可以包括alpha值的更改.有两个对象可用于创建渐变:CGShadingRef和CGGradient.
在两种创建渐变的方法中," CGGradient"类型更容易使用.它获取位置和颜色的列表,然后从该列表中为您计算渐变中每个点的颜色.我仅在代码示例中使用RGB颜色空间,因此将用于渐变的颜色空间选项. Apple的某些文档会将您引向CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);来执行此操作,但请忽略该操作.该功能已被弃用.相反,请使用" CGColorSpaceCreateDeviceRGB();".如果将以下代码添加到-(void)drawRect
函数的开头并重新运行该程序,则会在后台看到一个线性渐变.
//Gradient related variables
CGGradientRef myGradient;
CGColorSpaceRef myColorSpace;
size_t locationCount = 3;
CGFloat locationList[] = {0.0, 0.5, 1.0};
CGFloat colorList[] = {
1.0, 0.0, 0.5, 1.0, //red, green, blue, alpha
1.0, 0.0, 1.0, 1.0,
0.3, 0.5, 1.0, 1.0
};
myColorSpace = CGColorSpaceCreateDeviceRGB();
// CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
myGradient = CGGradientCreateWithColorComponents(myColorSpace, colorList,
locationList, locationCount);
//Paint a linear gradient
CGPoint startPoint, endPoint;
startPoint.x = 0;
startPoint.y = 0;
endPoint.x = CGRectGetMaxX(self.bounds)/2;
endPoint.y = CGRectGetMaxY(self.bounds)/2;
CGContextDrawLinearGradient(context, myGradient, startPoint, endPoint,0);
如果您想做一个径向渐变而不是线性渐变,那么您不必调用CGContextDrawLinearGradient(),而是需要调用
CGContextDrawRadialGradient().
//Radia Gradient Rendering
float startRadius = 20;
float endRadius = 210;
CGContextDrawRadialGradient(context, myGradient, startRadius,
startPoint, endRadius, endPoint, 0);
此径向渐变的第二个圆以屏幕的中心为中心.因此,渐变随圆停止.可选地,可以将渐变设置为继续延伸到圆之外或在第一个圆的开始之前延伸.为此,最后一个参数应包含选项" kCGGradientDrawsAfterEndLocation"以将渐变扩展到终点之外,或者选项" kCGGradientDrawsBeforeStartLocation"以将渐变扩展到起点之前的区域.将此选项与线性和径向渐变一起使用的结果如下所示.
使用CGShadingRef
``CGShadingRef
使用您创建的CGFunction
,其中包含用于计算渐变颜色的函数. CGShading对象还包含有关正在生成哪种类型的渐变(线性或径向)以及渐变的起点和终点的信息.一旦创建并填充了" CGShading"对象,就可以通过调用函数" CGContextDrawShading"来渲染渐变.
创建着色函数时,需要定义三个参数.该函数的返回类型为" void".
- 无效* info
- const float * inValue
- float * outValues 您的函数将被调用几次,其值的范围从整个梯度的长度范围内的已定义输入范围的低端到高端.对于我的示例,我将对输入值应用一个" sin"函数.
static void myCGFunction ( void * info, const float *in, float * outValue)
{
int componentCount = (int)info;
float phaseDelta = 2*3.1415963/(componentCount-1);
outValue[componentCount-1] = 1; //Set the alpha value to 1
for(int n=0;n<componentCount-1;++n)
{
outValue[n] = sin(phaseDelta*n+3.0*(*in));
}
}
定义此功能后,您需要将其打包到CGFunctionref结构中.您可以使用CGFunctionCreate函数来做到这一点.在下面的代码中,我初始化了一些变量以用作CGFunctionCreate的参数,并将指针传递给函数.最终结果存储在" myFunctionRef"中.
static const float inputRange[] = {0,1};
static const float outputRange[] = {0,1, 0,1, 0,1, 0,1 };
static const CGFunctionCallbacks callback = { 0, &myCGFunction, NULL};
// The total number of components needed is going to be one greater than
// the number of components in the selected color space.
int colorComponentCount = 1 + CGColorSpaceGetNumberOfComponents(myColorspace);
CGFunctionRef myFunctionRef =
//I'm passing the number of components as the option value
CGFunctionCreate((void*)colorComponentCount,
1,//The callback function takes one value for its input
inputRange, //The range of the values for
colorComponentCount,
outputRange,
&callback
);
通过使用CGFunctionRef对象,您可以使用CGShadingCreateAxial或CGShadingCreateRadial创建适当的CGSharingRef结构.然后使用" CGContextDrawShading"渲染渐变.
CGShadingRef myShading = CGShadingCreateAxial(myColorSpace,
startPoint, endPoint, myFunctionRef, false, false);
CGContextDrawShading(context, myShading);
模式
图案是一组在表面上反复重复的图形操作. Quartz 2D将一个区域划分为多个单元,并使用程序中定义的回调函数来渲染每个单元.单元格将具有统一的大小,并且单元格中的每一行和每一列之间可能都有一定的间距(取决于您有多少间距).有两种类型的图案:彩色图案和模版图案.模版图案就像掩模.它们本身没有颜色,但可以将颜色应用于颜色.认为它们就像橡皮图章一样;您可以在图章上施加任何颜色的墨水,而图章本身没有固有的颜色.定义图案后,其使用方式与纯色几乎相同. 首先,您需要定义一个渲染图案的函数.与阴影功能(在gradient部分中讨论)非常相似,第一个参数将是您定义的数据.下一个参数是要在其上呈现模式的上下文.该函数的原型定义如下:
typedef void (*CGPatternDrawPatternCallback) (
void *info,
CGContextRef context
);
使用图案时,必须设置色彩空间.这是通过CGContextSetFillColorSpace函数来完成的.除了上下文,此函数还带有一个" CGColorSpaceRef"对象.您可以使用CGContextSetFillColorSpace来创建它,并将NULL作为唯一参数.设置色彩空间后,可以通过调用CGColorSpaceRelease释放它.
void SetPatternColorSpace(CGContextRef context)
{
CGColorSpaceRef myColorSpace = CGContextSetFillColorSpace(NULL);
CGContextSetFillColorSpace(context, myColorSpace);
CGColorSpaceRelease(myColorSpace);
}
创建模式的功能需要很多参数.让我们看一下函数的原型,然后研究每个参数的含义:
CGPatternRef CGPatternCreate ( void *info,
CGRect bounds,
CGAffineTransform matrix,
float xStep,
float yStep,
CGPatternTiling tiling,
int isColored,
const CGPatternCallbacks *callbacks );
通常," info"参数包含您要传递给回调的数据. " bounds"参数包含模式中一个单元格的大小. " matrix"参数包含要应用于模式的变换" matrix".这可以用于缩放或旋转图案等操作.参数xStep和yStep包含要放置在图案单元之间的水平和垂直间距的数量.平铺参数可以具有三个值之一.
- kCGPatternTilingNoDistortion
- kCGPatternTilingConstantSpacingMinimalDistortion
- kCGPatternTilingConstantSpacing 如果图案是彩色图案,则将" isColored"设置为" true",如果是模板图案,则将其设置为" false".最后一个参数是CGPatternCallbacks结构.该结构定义如下:
struct CGPatternCallbacks
{
unsigned int version;
CGPatternDrawPatternCallback drawPattern;
CGPatternReleaseInfoCallback releaseInfo;
};
版本字段应设置为0.drawPattern是指向渲染函数的指针.如果在模式渲染完成后需要进行任何清理(释放内存),则指向您的清理函数的指针将进入releaseInfo
中.否则,此参数应为" NULL".对于我的示例,我正在创建一个简单的图案,该图案由正方形内的一个圆圈组成.我正在info
参数中传递模式的大小.
svoid MyPatternFunction(void* info, CGContextRef context)
{
CGRect* patternBoundaries = (CGRect*)info;
float myFillColor[] = {1,0,0,1}; //red;
CGContextSaveGState(context);
CGContextSetRGBFillColor(context, 0,1,1,1);
CGContextFillRect(context, *patternBoundaries);
CGContextSetFillColor(context, myFillColor);
CGContextFillEllipseInRect(context, *patternBoundaries);
CGContextFillPath(context);
CGContextRestoreGState(context);
}
将所有这些都付诸实践.我创建了一个名为" PaintMyPattern(CGContextRef,CGRect)“的函数,该函数接受必须进行渲染的内容以及要进行渲染的矩形区域.该功能及其依赖的功能如下:
//Forward declaration for top of implementation file. May not be necessary
//depending on where in your file that you past these functions
void MyPatternFunction(void* info, CGContextRef context);
void PaintMyPattern(CGContextRef context, CGRect targetRect);
void SetPatternColorSpace(CGContextRef context);
//The function definitions
void PaintMyPattern(CGContextRef context, CGRect targetRect)
{
CGPatternCallbacks callbacks = { 0, &MyPatternFunction, NULL };
CGContextSaveGState(context);
CGPatternRef myPattern;
SetPatternColorSpace(context);
CGRect patternRect = CGRectMake(0,0,32,32);
myPattern = CGPatternCreate((void*)&patternRect,
targetRect,
CGAffineTransformMake(1, 0, 0, 1, 0, 0),
32,
32,
kCGPatternTilingConstantSpacing,
true,
&callbacks
);
float alpha = 1;
CGContextSetFillPattern(context, myPattern, &alpha);
CGPatternRelease(myPattern);
CGContextFillRect(context, targetRect);
CGContextRestoreGState(context);
}
void SetPatternColorSpace(CGContextRef context)
{
CGColorSpaceRef myColorSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetFillColorSpace(context, myColorSpace);
CGColorSpaceRelease(myColorSpace);
}
void MyPatternFunction(void* info, CGContextRef context)
{
CGRect* patternBoundaries = (CGRect*)info;
float myFillColor[] = {1,0,0,1}; //red;
CGContextSaveGState(context);
CGContextSetRGBFillColor(context, 0,1,1,1);
CGContextFillRect(context, *patternBoundaries);
CGContextSetFillColor(context, myFillColor);
CGContextFillEllipseInRect(context, *patternBoundaries);
CGContextFillPath(context);
CGContextRestoreGState(context);
}
汇集全部
作为最后一个示例,我将重新制作一段时间前在Zune HD上制作的程序(该程序也位于here)在CodeProject.com上).该程序是一个简单的气泡级.我希望该程序的界面与Zune HD上的界面几乎相同.但是,与Zune HD不同,我想在不使用任何图形资源的情况下渲染所有界面.因此,所有接口都将通过Core Graphics调用进行渲染,以渲染渐变和图案.
乍一看,您将看到很多东西需要渲染.垂直和水平高度,中心为圆形高度.我可以使用相同的代码渲染垂直和水平级别.它只需要旋转其方向即可.因此,在分解过程中,此程序的渲染将导致三个渲染代码块:一个用于背景,一个用于垂直/水平级别,一个用于气泡级别. 在开始渲染之前,我想计算每个屏幕元素的位置.布局实际上是围绕方形屏幕设计的,并且可以水平或垂直拉伸.没有配备正方形屏幕的iOS设备,但是通过这种方式进行操作时,UI似乎能够很好地适应纵向和横向模式(这是我从Windows Mobile开发中学到的习惯).对于不存在的方形屏幕,我希望垂直和水平级别占据可用水平空间的四分之一和垂直空间的四分之一.圆形水准仪将在剩余空间的中心占据一个正方形区域.为了保持这些位置,我创建了三个成员CGRect元素,分别称为verticalLevelPosition,horizontalLevelPosition和circularLevelPosition.我的计算全部在名为-(void)updateElementPositioning`的方法中完成.
-(void)updateElementPositioning
{
float barWidth;
float circleWidth;
CGRect viewRect = self.bounds;
barWidth = MIN(viewRect.size.width, viewRect.size.height)/4;
circleWidth = barWidth*3;
verticalLevelPosition.size.width=barWidth;
verticalLevelPosition.size.height=viewRect.size.height-barWidth;
verticalLevelPosition.origin.y=barWidth;
verticalLevelPosition.origin.x=0;
horizontalLevelPosition.size.height=barWidth;
horizontalLevelPosition.size.width = viewRect.size.width;
horizontalLevelPosition.origin.x=0;
horizontalLevelPosition.origin.y=0;
circularLevelPosition.size.width =
circularLevelPosition.size.height = circleWidth;
circularLevelPosition.origin.x =
verticalLevelPosition.size.width+verticalLevelPosition.origin.x+
((viewRect.size.width - verticalLevelPosition.size.width-circleWidth)/2);
circularLevelPosition.origin.y =
horizontalLevelPosition.size.height+horizontalLevelPosition.origin.y+
((viewRect.size.height-horizontalLevelPosition.size.height-circleWidth)/2);
}
为了确保我的计算正确,我实现了一个-(void)drawRect:方法,该方法只用颜色填充这些矩形,以便可以看到它们的位置.结果就是我需要的.
-(void)drawRect:(CGRect)rect
{
float verticalRectColor[] = {1,0,0,1};
float horizontalRectColor[] = {0,1,0,1};
float circularRectColor[] = {0,0,1,1};
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColor(context,verticalRectColor );
CGContextFillRect(context, verticalLevelPosition);
CGContextSetFillColor(context, horizontalRectColor);
CGContextFillRect(context, horizontalLevelPosition);
CGContextSetFillColor(context, circularRectColor);
CGContextFillRect(context, circularLevelPosition);
}
如果您是Apple的纯粹主义者,并且相信所有Apple开发都应该使用Apple软件完成,那么您可能会不同意我要执行的后续步骤,因为我将在Windows系统上使用Microsoft软件.对于下一步,您可以使用所拥有的矢量编辑软件,因为这只是概念化我需要做的事情.下一步生成的文件不会被任何东西占用. 我已经启动了Microsoft Expressions Design,以便可以用它勾勒出我要组装的界面.对于矢量编辑程序中执行的许多操作,您会发现将大多数操作转换为几个API调用很容易.在玩了一段时间之后,我想到了以下设计.它由五个同心圆组成;三个具有线性渐变,一个具有径向渐变,另一个具有实心填充.最外面的圆具有一个直径,而另一个圆的内部直径稍小.其余三个圆具有相同的直径(比第二个圆的直径略小).
我正在创建一种新的方法来渲染循环水准仪.该功能需要在其上渲染的上下文,绑定级别的矩形以及在圆形级别周围放置的边距.现在,我只想确保我正确计算了绑定矩形.
-(void) drawCircularLevel:(CGContextRef)context :(CGRect)rect :(float) circleMargin
{
CGRect outerCircle;
CGRect middleCircle;
CGRect innerCircle;
float innerCircleColor[] = {1,0,0,0.7};
float middleCircleColor[] = {0, 1, 0, 0.7};
float outerCircleColor[] = {0, 0, 1, 0.7};
const float middleCircleFactor = 0.95;
const float innerCircleFactor = 0.90;
//calculate the rectangle binding the outer circle.
outerCircle = rect;
outerCircle.origin.x+=(outerCircle.size.width*circleMargin)/2;
outerCircle.origin.y+=(outerCircle.size.height*circleMargin)/2;
outerCircle.size.width*=(1-circleMargin);
outerCircle.size.height*=(1-circleMargin);
//calculate the rectangle binding the midle circle
middleCircle = outerCircle;
middleCircle.origin.x+=(outerCircle.size.width*(1-middleCircleFactor)/2);
middleCircle.origin.y+=(outerCircle.size.height*(1-middleCircleFactor)/2);
middleCircle.size.width=outerCircle.size.width*middleCircleFactor;
middleCircle.size.height=outerCircle.size.height*middleCircleFactor;
innerCircle = outerCircle;
innerCircle.origin.x+=(innerCircle.size.width*(1-innerCircleFactor)/2);
innerCircle.origin.y+=(innerCircle.size.height*(1-innerCircleFactor)/2);
innerCircle.size.width*=innerCircleFactor;
innerCircle.size.height*=innerCircleFactor;
CGContextSetFillColor(context, outerCircleColor);
CGContextFillEllipseInRect(context, outerCircle);
CGContextSetFillColor(context, middleCircleColor);
CGContextFillEllipseInRect(context, middleCircle);
CGContextSetFillColor(context, innerCircleColor);
CGContextFillEllipseInRect(context, innerCircle);
}
位置不错.所以现在我需要创建渐变. Microsoft Expressions Design以AARRGGBB格式表示颜色,其中每对字母都是00到FF之间的十六进制数字,表示颜色分量的强度. iOS接受浮点值中的颜色分量.因此,要将每种颜色转换为浮点值,我必须将其除以255.我使用的第一个渐变具有4个颜色点. 创建渐变并将其应用于渲染的圆后,我得到的外观几乎与Expressions Design中的外观相同.
我喜欢使用圆形水准仪得到的结果,然后继续制作垂直和水平水准仪.我希望关卡的末端比中间部分暗一些.为此,我在水平和垂直级别周围设置了一个裁剪区域,并在每个末端绘制了一个渐变圆.
CGContextSaveGState(context);
CGContextBeginPath(context);
CGContextAddRect(context, targetRect);
CGContextClip(context);
CGContextSetFillColor(context, levelBackgroundColor);
CGContextFillRect(context, targetRect);
CGContextSetFillColor(context, levelReflectionColor);
CGContextFillRect(context, reflectionRect);
CGContextDrawRadialGradient(context, shadingGradient,
gradientCenter1, 0, gradientCenter1, shadingRadius, 0);
CGContextDrawRadialGradient(context, shadingGradient,
gradientCenter2, 0, gradientCenter2, shadingRadius, 0);
我仍然要在关卡中渲染气泡.在渲染气泡之前,我需要先确定y的放置位置.气泡的位置将取决于加速度计的读数. (如果您不熟悉如何使用加速度计,请参阅my [第一篇文章](/KB/iPhone/iOSGetStarted00.aspx).)一旦获得了加速度计读数,便可以使用我在Zune HD加速度计中使用的相同数学.可以使用" atan2"函数来计算设备倾斜的角度,而倾斜度的大小可以使用P'y
thagorean定理来计算.大小可以是0到1(含)之间的” y".实际上,如果有人足够用力地晃动他们的设备,则读数可能会降低一次.我使用``MIN'‘函数将幅度限制为不大于1 by
.我给MIN函数赋予常数1和P’ythagoren定理的结果.一旦P'y
thagorean结果e’x超过1," MIN"值将只返回1,因为那是两个值中的较小者.我还有一个名为" levelPosition"的" CGPoint"对象,该对象将在其" x"和" y"成员中包含气泡相对于水平和垂直水平的相对位置.这些计算是在我处理加速度计消息的功能内完成的.计算完成后,代码将调用[self setNeedsDispla
y`];通知我们需要重绘屏幕的系统.
-(void)accelerometer:(UIAccelerometer *)accelerometer
didAccelerate:(UIAcceleration *)acceleration
{
tiltDirection = atan2(acceleration.y, acceleration.x);
tiltMagnitude = MIN(1, sqrt( acceleration.x*acceleration.x+
acceleration.y*acceleration.y));
levelPosition.y = sin(tiltDirection)*tiltMagnitude;
levelPosition.x = -cos(tiltDirection)*tiltMagnitude;
[self setNeedsDisplay];
}
气泡本身就是充满渐变的椭圆.完成的级别如下所示:
由于该程序使用加速度计,因此您需要将其部署到真实设备上才能正常工作.但是,当您运行该程序时,虽然获得了视觉效果,但是却明显出错了.该程序的Zune HD版本运行平稳(请观看视频here).但是该程序的版本运行不流畅.我们该如何解决?我将其保存为本文中有关使用核心动画功能的下一个补充.
下一步是什么
正如您可能已经通过关闭上一节的方式收集到的一样,对Core Animation的了解将是我研究的下一组图形API.与往常一样,请在下面的评论区域中留下您的想法,要求或注释,说明您认为需要进行的任何更正.
历史
- 2010年7月13日
- 2010年7月19日
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C Mobile Dev Intermediate iPhone Objective-C 新闻 翻译