12.2 Quartz 2D绘图
Quartz 2D绘图的核心API是CGContextRef,该API专门用于绘制各种图形。
12.2.1 Quartz 2D绘图基础:CGContextRef
使用Quartz 2D绘图的关键步骤有两步:获取CGContextRef;调用CGContextRef的方法进行绘图。
不同场景下获取CGContextRef的方式各不相同,下面介绍iOS开发中最常见的场景下如何获取CGContextRef。
1. 自定义UIView时获取CGContextRef
开发自定义UIView的方法是,开发一个继承UIView的子类,并重写该UIView的drawRect:方法,当该UIView每次显示出来时,或该UIView的内容需要更新时,系统都会自动调用该UIView的drawRect:方法。在调用drawRect:方法之前,系统会自动配置绘图环境,因此,程序只要通过如下函数即可获取CGContextRef绘图API:
- CGContextRef ctx = UIGraphicsGetCurrentContext();
获取CGContextRef之后,即可进行绘图。
需要指出的是,重写UIView的drawRect:方法绘图时,它的绘图API的坐标原点位于该控件的左上角,横向为X轴,X坐标越大,位置越向右;纵向为Y轴,Y坐标越大,位置越向下。
2. 创建位图时获取CGContextRef
如果需要在创建位图时获取CGContextRef,那么程序需要先调用UIGraphicsBeginImageContext()函数来创建内存中的图片。然后才能调用UIGraphicsGetCurrentContext()获取绘图的CGContextRef。例如如下代码。
- // 创建内存中的图片
- UIGraphicsBeginImageContext(CGSizeMake(320, 480));
- // 获取向内存中图片执行绘图的CGContextRef
- CGContextRef ctx = UIGraphicsGetCurrentContext();
Quartz 2D绘图的核心API是CGContextRef,该API并不是一个对象。由于Quartz 2D本身并不是面向对象的,它是面向过程的API,Quartz 2D提供了大量函数来完成绘图。
Quartz 2D提供的绘图函数如表12.2所示。
表12.2 Quartz 2D的绘图相关函数
函数签名 | 简要说明 |
void CGContextClearRect(CGContextRef c, CGRect rect); | 擦除指定矩形区域上绘制的图形 |
void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode); | 使用指定模式绘制当前CGContextRef中所包 含的路径。第二个参数支持 kCGPathFill、 kCGPathEOFill、kCGPathStroke、kCGPathFillStroke、kCGPathEOFillStroke等枚举值 |
void CGContextEOFillPath(CGContextRef c); | 使用奇偶规则来填充该路径包围的区域。奇 偶规则指:如果某个点被路径包围了奇数次, 系统绘制该点;如果被路径包围了偶数次,系统不绘制该点 |
void CGContextFillPath(CGContextRef c); | 填充该路径包围的区域 |
void CGContextFillRect(CGContextRef c,CGRect rect); | 填充rect代表的矩形 |
void CGContextFillRects(CGContextRef c,const CGRect rects[], size_t count); | 填充多个矩形 |
void CGContextFillEllipseInRect(CGContextRef context, CGRect rect); | 填充rect矩形的内切椭圆区域 |
void CGContextStrokePath(CGContextRef c); | 使用当前 CGContextRef设置的线宽绘制路径 |
void CGContextStrokeRect(CGContextRef c, CGRect rect); | 使用当前 CGContextRef设置的线宽绘制矩形框 |
void CGContextStrokeRectWithWidth(CGContextRef c, CGRect rect, CGFloat width); | 使用指定线宽绘制矩形框 |
void CGContextReplacePathWithStrokedPath(CGContextRef c); | 使用绘制当前路径时覆盖的区域作为当前CGContextRef 中的新路径。举例来说,假如当前CGContextRef包含一 个圆形路径且线宽为10,调用该方法后,当前CGContextRef 将包含一个环宽为10的环形路径 |
void CGContextStrokeEllipseInRect(CGContextRef context,CGRect rect); | 使用当前 CGContextRef设置的线宽绘制rect矩形的内切椭圆 |
void CGContextStrokeLineSegments(CGContextRef c, const CGPoint points[], size_t count); | 使用当前 CGContextRef设置的线宽绘制多条线段。该 方法需要传入2N个CGPoint组成的数组,其中1、2个点 组成第一条线段,3、4个点组成第2条线段,以此类推 |
表12.2中的大部分方法都涉及使用路径,路径由另一个API:CGPathRef来代表。CGPathRef代表任意多条直线或曲线连接而成的任意图形,当CGContextRef根据CGPathRef绘制时,它可以绘制出任意的形状。
关于如何利用CGContextRef来构建路径和添加路径,本节后面会有详细介绍,此处先用最简单的方法来绘制直线、矩形、椭圆等几何形状。
在绘图之前,还需要对绘图的颜色、线条粗细等属性进行设置,Quartz 2D提供了如表12.3所示的函数来设置绘图信息。
表12.3 设置绘图属性的相关函数
方法签名 | 简要说明 |
void CGContextSaveGState(CGContextRef c); | 保存CGContextRef当前的绘图状态,方便以后恢复该状态 |
void CGContextRestoreGState(CGContextRef c); | 把CGContextRef的状态恢复到最近一次保存时的状态 |
CGInterpolationQuality CGContextGetInterpolation Quality(CGContextRef c); | 获取当前CGContextRef在放大图片时的插值质量 |
void CGContextSetInterpolationQuality(CGContextRef c, CGInterpolationQuality quality); | 设置当前CGContextRef在放大图片时的插值质量 |
void CGContextSetLineCap(CGContextRef c, CGLineCap cap); | 设置线段端点的绘制形状。该属性支持如下三个值。 kCGLineCapButt:该属性值指定不绘制端点, 线条结尾处直接结束。这是默认值。 kCGLineCapRound:该属性值指定绘制圆形端点, 线条结尾处绘制一个直径为线条宽度的半圆。 kCGLineCapSquare:该属性值指定绘制方形端点。 线条结尾处绘制半个边长为线条宽度的正方形。需要 说明的是,这种形状的端点与“butt”形状的端点十分相似, 只是采用这种形式的端点的线条略长一点而已 |
void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat lengths[],size_t count); | 设置绘制边框时所用的点线模式,Quartz 2D支 持非常强大的点线模式。后面会有详细介绍 |
void CGContextSetLineJoin(CGContextRef c, CGLineJoin join); | 设置线条连接点的风格,该属性支持如下三个值: kCGLineJoinMeter:这是默认的属性值。 该方格的连接点形状如所示。 kCGLineJoinRound:该方格的连接点形状如 所示。 kCGLineJoinBevel:该方格的连接点形状如 所示 |
void CGContextSetLineWidth(CGContextRef c, CGFloat width); | 设置绘制直线、边框时的线条宽度 |
void CGContextSetMiterLimit(CGContextRef c, CGFloat limit); | 当把连接点风格设为meter风格时,该方法用于控制锐角箭头的长度 |
void CGContextSetPatternPhase(CGContextRef c, CGSize phase); | 设置该CGContextRef采用位图填充的相位 |
void CGContextSetFillPattern(CGContextRef c, CGPatternRef pattern,const CGFloat components[]); | 设置该CGContextRef使用位图填充 |
void CGContextSetShouldAntialias(CGContextRef c, bool shouldAntialias); | 设置该CGContextRef是否应该抗锯齿(即光滑图形曲线边缘) |
void CGContextSetStrokePattern(CGContextRef c, CGPatternRef pattern, const CGFloat components[]); | 设置该CGContextRef使用位图绘制线条、边框 |
续表
方法签名 | 简要说明 | ||
void CGContextSetBlendMode(CGContextRef context,CGBlendMode mode); | 设置CGContextRef的叠加模式。Quartz 2D 支持多种叠加模式,后面专门有关于叠加模式的介绍 | ||
void CGContextSetAllowsAntialiasing(CGContextRef context, bool allowsAntialiasing); | 设置该CGContextRef是否允许抗锯齿 | ||
void CGContextSetAllowsFontSmoothing(CGContextRef context, bool allowsFontSmoothing); | 设置该CGContextRef是否允许光滑字体 | ||
void CGContextSetShouldSmoothFonts(CGContextRef c,bool shouldSmoothFonts); | 设置该CGContextRef是否允许光滑字体 | ||
void CGContextSetAlpha (CGContextRef c, CGFloat alpha); | 设置全局透明度 | ||
void CGContextSetCMYKFillColor(CGContextRef c, CGFloat cyan, CGFloat magenta, CGFloat yellow,CGFloat black, CGFloat alpha); | 使用CMYK颜色模式来设置该CGContextRef的填充颜色 | ||
void CGContextSetCMYKStrokeColor(CGContextRef c, CGFloat cyan, CGFloat magenta, CGFloat yellow,CGFloat black, CGFloat alpha); | 使用CMYK颜色模式来设置该CGContextRef的线条颜色 | ||
void CGContextSetFillColorWithColor(CGContextRef c, CGColorRef color); | 使用指定颜色来设置该CGContextRef的填充颜色 | ||
void CGContextSetStrokeColorWithColor(CGContextRef c, CGColorRef color); | 使用指定颜色来设置该CGContextRef的线条颜色 | ||
void CGContextSetGrayFillColor(CGContextRef c, CGFloat gray, CGFloat alpha); | 使用灰色来设置该CGContextRef的填充颜色 | ||
void CGContextSetGrayStrokeColor(CGContextRef c, CGFloat gray, CGFloat alpha); | 使用灰色来设置该CGContextRef的线条颜色 | ||
void CGContextSetRGBFillColor(CGContextRef c, CGFloat gray, CGFloat alpha); | 使用RGB颜色模式来设置该CGContextRef的填充颜色 | ||
void CGContextSetRGBStokeColor(CGContextRef c, CGFloat gray, CGFloat alpha); | 使用RGB颜色模式来设置该CGContextRef的线条颜色 | ||
void CGContextSetShadow(CGContextRef context, CGSize offset, CGFloat blur); | 设置阴影在X、Y方向上的偏移,以及模糊度(blur 值越大,阴影越模糊)。该函数没有设置阴影颜色, 默认使用1/3透明的黑色(即RGBA{0, 0, 0, 1.0/3.0})作为阴影颜色 | ||
void CGContextSetShadowWithColor(CGContextRef context, CGSize offset, CGFloat blur, CGColorRef color); | 设置阴影在X、Y方向上的偏移,以及模糊度和阴影的颜色 |
12.2.2 绘制几何图形
从表12.2不难看出,Quartz 2D只提供了方法来绘制线段、矩形和椭圆,如果需要绘制更复杂的图形,则需要借助路径。下面的程序先使用这些方法来绘制几个简单的图形。
新建一个Single View Application,该应用包含应用程序委托类、视图控制器类和配套的S
Storyboard界面设计文件,本例无须修改应用程序委托和视图控制器类,只需要将Storyboard界面文件中最大的UIView控件类改为使用FKGeometryView自定义类即可。
FKGeometryView是由UIView派生出来的一个子类,我们将会重写drawRect:方法进行绘图,该控件类的实现部分如下。
程序清单:codes/12/12.2/GeometryShape/GeometryShape/FKGeometryView.m
- @implementation FKGeometryView
- // 重写该方法进行绘图
- - (void)drawRect:(CGRect)rect
- {
- CGContextRef ctx = UIGraphicsGetCurrentContext(); // 获取绘图上下文
- CGContextSetLineWidth(ctx, 16); // 设置线宽
- CGContextSetRGBStrokeColor(ctx, 0 , 1, 0 , 1);
- // ----------下面绘制3个线段测试端点形状-----------
- // 定义4个点,绘制线段
- const CGPoint points1[] = {CGPointMake(10 , 20), CGPointMake(100 , 20)
- ,CGPointMake(100 , 20) , CGPointMake(20, 50)};
- CGContextStrokeLineSegments(ctx ,points1 , 4); // 绘制线段(默认不绘制端点)
- CGContextSetLineCap(ctx, kCGLineCapSquare); // 设置线段的端点形状:方形端点
- const CGPoint points2[] = {CGPointMake(110 , 20), CGPointMake(200 , 20)
- ,CGPointMake(200 , 20) , CGPointMake(120, 50)}; // 定义4个点,绘制线段
- CGContextStrokeLineSegments(ctx ,points2 , 4); // 绘制线段
- CGContextSetLineCap(ctx, kCGLineCapRound); // 设置线段的端点形状:圆形端点
- const CGPoint points3[] = {CGPointMake(210 , 20), CGPointMake(300 , 20)
- ,CGPointMake(300 , 20) , CGPointMake(220, 50)}; // 定义4个点,绘制线段
- CGContextStrokeLineSegments(ctx ,points3 , 4); // 绘制线段
- // ----------下面绘制3个线段测试点线模式-----------
- CGContextSetLineCap(ctx, kCGLineCapButt); // 设置线段的端点形状
- CGContextSetLineWidth(ctx, 10); // 设置线宽
- CGFloat patterns1[] = {6 , 10};
- CGContextSetLineDash(ctx , 0 , patterns1 , 1); // 设置点线模式:实线宽6,间距宽10
- const CGPoint points4[] = {CGPointMake(40 , 65), CGPointMake(280 , 65)}; // 定义两个点,绘制线段
- CGContextStrokeLineSegments(ctx ,points4 , 2); // 绘制线段
- // 设置点线模式:实线宽6,间距宽10,但第1个实线宽为3
- CGContextSetLineDash(ctx , 3 , patterns1 , 1);
- // 定义两个点,绘制线段
- const CGPoint points5[] = {CGPointMake(40 , 85), CGPointMake(280 , 85)};
- CGContextStrokeLineSegments(ctx ,points5 , 2); // 绘制线段
- CGFloat patterns2[] = {5,1,4,1,3,1,2,1,1,1,1,2,1,3,1,4,1,5};
- CGContextSetLineDash(ctx , 0 , patterns2 , 18); // 设置点线模式
- const CGPoint points6[] = {CGPointMake(40 , 105), CGPointMake(280 , 105)};
- CGContextStrokeLineSegments(ctx ,points6 , 2); // 绘制线段
- // ---------下面填充矩形---------
- // 设置线条颜色
- CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor);
- CGContextSetLineWidth(ctx, 14); // 设置线条宽度
- CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor); // 设置填充颜色
- CGContextFillRect(ctx , CGRectMake(30 , 120 , 120 , 60)); // 填充一个矩形
- // 设置填充颜色
- CGContextSetFillColorWithColor(ctx, [UIColor yellowColor].CGColor);
- CGContextFillRect(ctx, CGRectMake(80 , 160 , 120 , 60)); // 填充一个矩形
- // ---------下面绘制矩形边框---------
- CGContextSetLineDash(ctx, 0, 0, 0); // 取消设置点线模式
- CGContextStrokeRect(ctx, CGRectMake(30 , 230 , 120 , 60)); // 绘制一个矩形边框
- // 设置线条颜色
- CGContextSetStrokeColorWithColor(ctx, [UIColor purpleColor].CGColor);
- CGContextSetLineJoin(ctx, kCGLineJoinRound); // 设置线条连接点的形状
- CGContextStrokeRect(ctx , CGRectMake(80 , 260 , 120 , 60)); // 绘制一个矩形边框
- CGContextSetRGBStrokeColor(ctx, 1.0, 0, 1.0 , 1.0); // 设置线条颜色
- CGContextSetLineJoin(ctx, kCGLineJoinBevel); // 设置线条连接点的形状
- CGContextStrokeRect(ctx , CGRectMake(130 , 290 , 120 , 60)); // 绘制一个矩形边框
- CGContextSetRGBStrokeColor(ctx, 0, 1 , 1 , 1); // 设置线条颜色
- // ---------下面绘制和填充一个椭圆---------
- // 绘制一个椭圆
- CGContextStrokeEllipseInRect(ctx , CGRectMake(30 , 380 , 120 , 60));
- CGContextSetRGBFillColor(ctx, 1, 0 , 1 , 1); // 设置填充颜色
- // 填充一个椭圆
- CGContextFillEllipseInRect(ctx , CGRectMake(180 , 380 , 120 , 60));
- }
- @end
程序中的前5行代码依次绘制了三组线段,分别测试不同的线条端点,其中第一组线段使用默认的端点风格:butt风格的端点;第二组线条使用方形端点风格:square风格的端点;第三组线条使用圆形端点风格:round风格的端点。
接下来的6行粗体字代码分别使用不同的点线模式绘制了三条横线。Quartz 2D支持功能丰富的点线模式,下一节会详细介绍Quartz 2D的点线模式。
接着的两行粗体字代码使用不同的颜色分别填充了两个矩形。
再接下来的6行粗体字代码绘制了三个矩形边框,第一个矩形边框使用了默认的连接点风格:meter风格;第二个矩形边框使用了round连接点风格;第三个矩形边框使用了bevel连接点风格。
最后的两行粗体字代码分别绘制了一个椭圆边框和填充了一个椭圆区域。
编译、运行该程序,将会看到如图12.2所示的效果。