您的位置:首页 > 移动开发 > IOS开发

【IOS 开发学习总结-OC-65】Quartz 2D绘图(4-2)——绘制文本+设置阴影+使用路径

2015-10-23 16:39 573 查看

【IOS 开发学习总结-OC-65】Quartz 2D绘图(中)——绘制文本+设置阴影+使用路径

绘制文本

CGContextRef为绘制文字提供了如下函数。

CGAffineTransform CGContextGetTextMatrix(CGContextRef c):获取当前对文本执行变换的变换矩阵。

CGPoint CGContextGetTextPosition(CGContextRef c):获取该CGContextRef中当前绘制文本的位置。

CGContextSelectFont(CGContextRef c, const char *name, CGFloat size, CGTextEncoding textEncoding):设置该CGContextRef当前绘制文本的字体、字体大小。

void CGContextSetCharacterSpacing(CGContextRef c, CGFloat spacing):设置该CGContextRef中绘制文本的字符间距。

void CGContextSetFont(CGContextRef c, CGFontRef font):设置该CGContextRef中绘制文本的字体。

void CGContextSetFontSize(CGContextRef c, CGFloat size):设置该CGContextRef中绘制文本的字体大小。

CGContextSetTextDrawingMode(CGContextRef c, CGTextDrawingMode mode):设置该CGContextRef绘制文本的绘制模式。该函数支持kCGTextFill、kCGTextStroke、kCGTextFillStroke等绘制模式。

void CGContextSetTextMatrix(CGContextRef c, CGAffineTransform t):设置对将要绘制的文本执行指定的变换。

CGContextSetTextPosition(CGContextRef c , CGFloat x, CGFloat y):设置CGContextRef的一个文本的绘制位置。

void CGContextShowText(CGContextRef c,

const char *string,

size_t length):控制CGContextRef在当前绘制点绘制指定文本。

void CGContextShowTextAtPoint (CGContextRef c,

CGFloat x,

CGFloat y,

const char *string,

size_t length):控制CGContextRef在指定绘制点绘制指定文本。

除此之外,系统还为NSString提供了一个UIStringDrawing分类,通过该分类为NSString增加了drawAtPoint:withAttributes:、drawInRect:withAttributes:等绘制方法,通过这些绘制方法可以更方便地绘制字符串。

总结起来,使用CGContextRef绘制文本的步骤如下。

获取绘图的CGContextRef。

设置绘制文本的相关属性,例如,绘制文本所用的绘制方式、字体大小、字体名称等。

如果只是绘制不需要进行变换的文本,直接调用NSString的drawAtPoint:withAttributes:、drawInAttributes:withFont:等方法绘制即可。如果需要对绘制的文本进行变换,则需要先调用CGContextSetTextMatrix()函数设置变换矩阵,再调用CGContextShowTextAtPoint()方法绘制文本。

下面的程序示范了如何利用Quartz 2D绘制文本。首先创建一个Single View Application,该Application包含一个应用程序委托代理类、一个视图控制器和配套的Storyboard界面设计文件。将该界面设计文件中最大的View改为使用自定义的FKTextView类,并向该界面设计文件添加两个UISlider控件。为了让这两个UISlider控件控制文本的缩放和旋转角度,将控制缩放的UISlider的最小值、最大值分别设置为0.1、5;将控制旋转的UISlikder的最小值、最大值设分别为-90、90,程序为两个UISlider的Value Changed事件分别绑定scaleChanged:和rotateChanged: IBActino方法。

下面是该应用的视图控制器类的实现代码。

程序清单:codes/12/12.2/DrawTextTest/DrawTextTest/FKViewController.m

@implementation FKViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
}
- (IBAction)scaleChanged:(id)sender
{
// 修改FKTextView的scaleRate属性
((FKTextView*)self.view).scaleRate = ((UISlider*)sender).value;
}
- (IBAction)rotateChanged:(id)sender
{
// 修改FKTextView的rotateAngle属性
((FKTextView*)self.view).rotateAngle = ((UISlider*)sender).value;
}
@end


从上面的控制器代码可以看出,当用户拖动界面的UISlider时,视图控制器中的监听器方法就会改变FKTextView控件的scaleRate、rotateAngle属性。当这些属性被修改时,FKTextView就会使用特定的缩放比、旋转角重绘文本。下面是FKTextView类的实现代码。

程序清单:codes/12/12.2/DrawTextTest/DrawTextTest/FKTextView.m

@implementation FKTextView
- (void)setScaleRate:(CGFloat)scaleRate
{
if(_scaleRate != scaleRate)
{
_scaleRate = scaleRate;
[self setNeedsDisplay];  // 通知该控件重绘自己
}
}
- (void)setRotateAngle:(CGFloat)rotateAngle
{
if(_rotateAngle != rotateAngle)
{
_rotateAngle = rotateAngle;
[self setNeedsDisplay];  // 通知该控件重绘自己
}
}
// 重写该方法绘制该控件
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();  // 获取该控件的绘图CGContextRef
CGContextSetCharacterSpacing (ctx, 4);  // 设置字符间距
CGContextSetRGBFillColor (ctx, 1, 0, 1, 1);  // 设置填充颜色
CGContextSetRGBStrokeColor (ctx, 0, 0, 1, 1);  // 设置线条颜色
CGContextSetTextDrawingMode (ctx, kCGTextFill);  // 设置使用填充模式绘制文字
// 绘制文字
[@"疯狂iOS讲义" drawAtPoint:CGPointMake(10 ,20)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:@"Arial Rounded MT Bold" size: 45],
NSFontAttributeName,
[UIColor magentaColor] , NSForegroundColorAttributeName , nil]];
// 设置使用描边模式绘制文字
CGContextSetTextDrawingMode (ctx, kCGTextStroke);
// 绘制文字
[@"疯狂Android讲义" drawAtPoint:CGPointMake(10 ,80)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:@"Heiti SC" size: 40],NSFontAttributeName,
[UIColor blueColor] , NSForegroundColorAttributeName , nil]];
// 设置使用填充、描边模式绘制文字
CGContextSetTextDrawingMode (ctx, kCGTextFillStroke);
// 绘制文字
[@"疯狂Ajax讲义" drawAtPoint:CGPointMake(10 ,130)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:@"Heiti SC" size: 50], NSFontAttributeName,
[UIColor magentaColor] , NSForegroundColorAttributeName , nil]];
// 定义一个垂直镜像的变换矩阵
CGAffineTransform yRevert = CGAffineTransformMake(1, 0, 0, -1, 0, 0);
// 设置绘制文本的字体和字体大小
CGContextSelectFont (ctx, "Courier New" , 40, kCGEncodingMacRoman);
// 为yRevert变换矩阵根据scaleRate添加缩放变换矩阵
CGAffineTransform scale = CGAffineTransformScale(yRevert,
self.scaleRate, self. scaleRate);
// 为scale变换矩阵根据rotateAngle添加旋转变换矩阵
CGAffineTransform rotate = CGAffineTransformRotate(scale,
M_PI * self.rotateAngle / 180);
CGContextSetTextMatrix(ctx, rotate);  // 对CGContextRef绘制文字时应用变换
CGContextShowTextAtPoint(ctx, 50, 300, "crazyit.org", 11);  // 绘制文本
}
@end


上面的程序中,前三行粗体字代码直接调用了NSString的drawAtPoint:withAttributes:方法进行绘制–这是一种非常简单的绘制方式。

接下来需要对绘制的文本执行坐标变换,因此程序中先用CGAffineTransformMake()函数定义了一个变换矩阵:(1, 0, 0, -1, 0, 0),该变换矩阵将会对绘制的文本做垂直镜像。此处这么做的原因是因为Quartz 2D本身的坐标系统与UIView控件绘图的坐标系统并不相同,Quartz 2D为了照顾开发者的习惯,已经对坐标系统做过变换,但它在处理文本变换时少了一次垂直镜像变换,导致绘制出来的文本总是上下颠倒的。为了解决这个问题,笔者先对文本做了一次垂直镜像变换。

关于坐标变换的详细理论,后面会详细阐述,读者可参考相关内容。

然后利用CGAffineTransformScale()、CGAffineTransformRotate()增加了缩放、旋转变换,程序最后一行粗体字代码调用CGContextShowTextAtPoint()方法绘制了文本,该文本将会带有缩放和旋转效果。

上面的程序中,在绘制文本时,使用了大量iOS支持的字体,如果读者需要获取iOS支持的所有字体,可调用UIFont类的familyNames类方法,该方法返回一个数组,该数组包含iOS所支持的全部字体。

编译、运行该程序,用户即可通过拖动界面上的UISlider控件来改变文本的缩放、旋转,效果如图12.5所示。



设置阴影

前面已经介绍了CGContextRef为设置图形阴影提供了如下两个函数。

void CGContextSetShadow(CGContextRef context, CGSize offset, CGFloat blur):该函数设置阴影在X、Y方向上的偏移,并设置阴影的模糊程度。该函数的offset包含两个CGFloat值,第1个CGFloat值控制阴影在X方向的偏移,如果该值为正,则向右偏移,否则向左偏移;第2个CGFloat值控制阴影在Y方向的偏移,如果该值为正,则向下偏移,否则向上偏移。最后一个blur参数控制阴影的模糊程度,如果blur参数为1,表明阴影几乎不模糊,blur参数越大,阴影越模糊。

void CGContextSetShadowWithColor(CGContextRef context, CGSize offset, CGFloat blur, CGColorRef color):该函数与前一个函数的功能基本相似,只是该函数多了一个属性用于设置阴影颜色。

下面的程序示范了为所绘制的形状添加阴影。首先创建一个Single View Application,该Application包含一个应用程序委托代理类、一个视图控制器和配套的Storyboard界面设计文件。将该界面设计文件中最大的View改为使用自定义的FKShadowView类。该程序的控制器类几乎无须修改,只要重写FKShadowView的drawRect:方法,在该方法中设置阴影,并绘制带阴影的形状和文本即可。下面是FKShadowView类的实现代码。

程序清单:codes/12/12.2/ShadowTest/ShadowTest/FKShadowView.m

@implementation FKShadowView
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();  // 获取绘图的CGContextRef
// 使用默认的阴影颜色,阴影向左上角投影,模糊度为5
CGContextSetShadow(ctx, CGSizeMake(8, -6), 5);
CGContextSetRGBFillColor (ctx, 1, 0, 1, 1);  // 设置填充颜色
CGContextSetRGBStrokeColor (ctx, 0, 0, 1, 1);  // 设置线条颜色
CGContextSetTextDrawingMode (ctx, kCGTextFill);  // 设置使用填充模式绘制文字
// 绘制文字
[@"疯狂iOS讲义" drawAtPoint:CGPointMake(10 ,20)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:@"Arial Rounded MT Bold" size: 45]
, NSFontAttributeName ,
[UIColor magentaColor] , NSForegroundColorAttributeName, nil]];
// 设置使用描边模式绘制文字
CGContextSetTextDrawingMode (ctx, kCGTextStroke);
// 绘制文字
[@"疯狂Android讲义" drawAtPoint:CGPointMake(10 ,80)
withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:@"Heiti SC" size: 40], NSFontAttributeName ,
[UIColor magentaColor] , NSForegroundColorAttributeName, nil]];
// 使用默认的阴影颜色,阴影向右下角投影,模糊度为20
CGContextSetShadowWithColor(ctx, CGSizeMake(10, 8), 10
, [[UIColor redColor] CGColor]);
CGContextFillRect(ctx, CGRectMake(20 , 150 , 180 , 80));
CGContextSetRGBStrokeColor (ctx, 1, 0, 1, 1);  // 设置线条颜色
CGContextStrokeRect(ctx, CGRectMake(30 , 260 , 180 , 80));
}


上面程序中的第一行粗体字代码使用默认的阴影颜色,通过传入CGSizeMake(8, -6)参数控制阴影向左上角投影;第二行粗体字代码使用红色作为阴影颜色,通过传入CGSizeMake(10, 8)参数控制阴影向右下角投影。编译、运行该程序,即可看到如图12.6所示的效果。



使用路径

正如前面提到的,Quartz 2D只提供了绘制矩形、椭圆的函数,并没有直接提供绘制圆角矩形、三角形等几何图形的函数。为了绘制更复杂的图形,必须启用路径。

表12.4列出了Quartz 2D为创建路径提供的函数。

表12.4 创建路径的相关函数



除此之外,Quartz 2D还提供了如下函数来获取当前CGContextRef所包含的路径信息。

bool CGContextIsPathEmpty(CGContextRef c):该函数用于判断指定CGContextRef包含的路径是否为空。

CGPoint CGContextGetPathCurrentPoint(CGContextRef c):该函数用于返回指定CGContextRef包含的路径的当前点。

CGRect CGContextGetPathBoundingBox(CGContextRef c):该函数用于返回指定CGContextRef中能完整包围所有路径的最小矩形。

bool CGContextPathContainsPoint(CGContextRef context, CGPoint point, CGPathDrawingMode mode):该函数判断指定CGContextRef包含的路径按指定绘制模式进行绘制时,是否需要绘制point点。

为了在Canvas中使用路径,可按如下步骤进行。

调用CGContextBeginPath()函数开始定义路径。

调用表12.4所示的各种函数添加子路径。

如果路径添加完成,调用CGContextClosePath()函数关闭路径。

调用CGContextDrawPath()、CGContextEOFillPath()、CGContextFillPath()或CGContextStrokePath()函数来填充路径或绘制路径边框即可。在这些方法中,第一个方法可以代替后面的几个方法,第一个方法指定使用特定的模式来绘制图形,它支持如下几种绘制方式。

kCGPathFill:指定填充路径。相当于CGContextFillPath()函数。

kCGPathEOFill:指定采用even-odd模式填充路径。相当于CGContextEOFillPath()函数。

kCGPathStroke:指定只绘制路径。相当于CGContextStrokePath()函数。

kCGPathFillStroke:指定既绘制路径,也填充路径。如果采用这种绘制方式,必须使用该函数。

kCGPathEOFillStroke:指定既绘制路径,也采用even-odd模式填充路径。如果采用这种绘制方式,必须使用该函数。

通过上面介绍的CGContextAddArc()函数,我们可以绘制扇形,下面的程序代码使用循环绘制了10个扇形,而且这10个扇形的透明度逐渐降低。首先创建一个Single View Application,该Application包含一个应用程序委托代理类、一个视图控制器和配套的Storyboard界面设计文件。将该界面设计文件中最大的View改为使用自定义的FKArcView类。该程序的控制器类几乎无须修改,只要重写FKArcView的drawRect:方法,在该方法中使用路径绘制扇形即可。

下面是FKArcView类的实现代码。

程序清单:codes/12/12.2/AddArcTest/AddArcTest/FKArcView.m

@implementation FKArcView

- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
for(int i = 0 ; i < 10 ; i++)
{
CGContextBeginPath(ctx);  // 开始定义路径
// 添加一段圆弧,最后一个参数1代表逆时针,0代表顺时针
CGContextAddArc(ctx , i * 25 , i * 25, (i + 1) * 8 , M_PI * 1.5 , M_PI , 0);
CGContextClosePath(ctx);  // 关闭路径
CGContextSetRGBFillColor(ctx, 1, 0, 1, (10 - i) * 0.1);  // 设置填充颜色
CGContextFillPath(ctx);  // 填充当前路径
}
}
@end


上面程序中的粗体字代码绘制了从M_PI * 1.5角度(12点方向)开始,到M_PI角度(9点方向)结束的扇形–在该绘图API中0度代表X轴方向(也就是3点方向)。需要指出的是,关于CGContextAddArc()函数的最后一个参数,官方文档介绍该参数为0代表逆时针,但笔者运行时看到的效果是0代表顺指针(可参见下面的运行效果)。

编译、运行该程序,可以看到如图12.7所示的效果。



从上面程序的运行结果来看,相信读者对CGContextAddArc (CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)函数的用法已经比较清楚,该方法的第2、3个参数指定圆弧的圆心,第4个参数用于设置圆弧的半径,第5、6个参数则用于设置圆弧的开始角度、结束角度,最后一个参数用于设置是否顺时针旋转。

可能有读者对CGContextAddArcToPoint(CGContextRef c, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius)函数感到疑惑,该方法也是添加一段圆弧路径。确定这段圆弧的方式是:假设从当前点到P1(x1、y1)绘制一条线条,再从 P1(x1、y1)到P2(x2、y2)绘制一条线条,CGContextAddArcToPoint()则绘制一条同时与上面两条线条相切且半径为radius的圆弧。该函数方法的示意图如图12.8所示。



借助CGContextAddArcToPoint()和CGContextAddLineToPoint()函数可以非常容易地实现添加圆角矩形路径的方法(所谓圆角矩形,就是在矩形的每个角都绘制一段90°的圆弧)。除此之外,还有CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)函数用于把绘制点移动到指定位置,借助这些方法还可以绘制任意的多角星。

下面的程序代码将会开发两个自定义函数,一个用于添加圆角矩形,一个用于添加多角星。程序代码如下。

程序清单:codes/12/12.2/PathTest/FKContext.h

#ifndef PathTest_FKContext_h
#define PathTest_FKContext_h
/*
该方法负责绘制圆角矩形。x1、y2:是圆角矩形左上角的座标;width、height:控制圆角矩行的宽、高;
radius:控制圆角矩形的四个圆角的半径
*/
void CGContextAddRoundRect(CGContextRef c, CGFloat x1 , CGFloat y1
, CGFloat width , CGFloat height , CGFloat radius)
{
CGContextMoveToPoint (c, x1 + radius , y1);  // 移动到左上角
CGContextAddLineToPoint(c , x1 + width - radius, y1);  // 添加一条连接到右上角的线段
// 添加一段圆弧
CGContextAddArcToPoint(c , x1 + width , y1, x1 + width, y1 + radius, radius);   // 添加一条连接到右下角的线段
CGContextAddLineToPoint(c , x1 + width, y1 + height - radius);
// 添加一段圆弧
CGContextAddArcToPoint(c , x1 + width, y1 + height
, x1 + width - radius , y1 + height , radius);
// 添加一条连接到左下角的线段
CGContextAddLineToPoint(c , x1 + radius, y1 + height);
// 添加一段圆弧
CGContextAddArcToPoint(c , x1, y1 + height , x1, y1 + height - radius , radius);    // 添加一条连接到左上角的线段
CGContextAddLineToPoint(c , x1 , y1 + radius);
// 添加一段圆弧
CGContextAddArcToPoint(c , x1 , y1 , x1 + radius , y1 , radius);
}
/*
该方法负责绘制多角星。n:该参数通常应设为奇数,控制绘制N角星。
dx、dy:控制N角星的中心。size:控制N角星的大小
*/
void CGContextAddStar(CGContextRef c , NSInteger n,
CGFloat dx , CGFloat dy , NSInteger size)
{
CGFloat dig = 4 * M_PI / n ;
CGContextMoveToPoint(c , dx , dy + size);  // 移动到指定点
for(int i = 1 ; i <= n ; i++)
{
CGFloat x = sin(i * dig);
CGFloat y = cos(i * dig);
// 绘制从当前点连接到指定点的线条
CGContextAddLineToPoint(c , x * size + dx ,y * size + dy);
}
}
#endif


上面两个函数都没有执行任何绘制操作,它们都只是向当前CGContextRef中添加路径,第一个函数用于添加一个圆角矩形路径,第二个函数用于添加一个多角星路径。

上面这个函数库与项目无关,该函数库中包含的两个函数完全可以在任何项目中复用。因此,如果读者以后的项目需要绘制圆角矩形、多角星,只要将该文件复制到项目中,并导入该函数库,即可调用这两个函数添加路径。

实例:绘制任意多角星

下面创建一个应用来绘制圆角矩形和多角星。首先创建一个Single View Application,该Application包含一个应用程序委托代理类、一个视图控制器和配套的Storyboard界面设计文件。将该界面设计文件中最大的View改为使用自定义的FKPathView类。该程序的控制器类几乎无须修改,只要重写FKPathView的drawRect:方法,在该方法中调用FKContext.h中的方法来添加圆角矩形、多角星路径,然后根据需要采用不同的方式绘制这些路径即可。

下面是FKPathView类的实现代码。

程序清单:codes/12/12.2/PathTest/FKPathView.m

#import "FKPathView.h"
#import "FKContext.h"
@implementation FKPathView
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();  // 获取绘图CGContextRef
CGContextBeginPath(ctx);  // 开始添加路径
CGContextAddStar(ctx, 5, 80, 150, 40);  // 添加一个五角星的路径
CGContextAddRoundRect(ctx, 10, 30, 150, 70, 14);  // 添加一个圆角矩形的路径
CGContextClosePath(ctx);  // 关闭路径
CGContextSetRGBStrokeColor(ctx, 1, 1, 0, 1);  // 设置线条颜色
CGContextSetLineWidth(ctx, 4);  // 设置线宽
CGContextStrokePath(ctx);  // 绘制路径
CGContextBeginPath(ctx);  // 开始添加路径
CGContextAddStar(ctx, 5, 240, 150, 40);  // 添加一个五角星的路径
CGContextAddRoundRect(ctx, 170, 30, 130, 70, 14);  // 添加一个圆角矩形的路径
CGContextClosePath(ctx);  // 关闭路径
CGContextSetRGBFillColor(ctx, 1, 0, 1, 1);  // 设置填充颜色
CGContextDrawPath(ctx, kCGPathFillStroke);  // 采用填充并绘制路径的方式来绘制路径
CGContextBeginPath(ctx);  // 开始添加路径
CGContextAddStar(ctx, 3, 60, 220, 40);  // 添加一个三角形的路径
CGContextClosePath(ctx);  // 关闭路径
CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);  // 设置填充颜色
CGContextFillPath(ctx);  // 填充路径
CGContextBeginPath(ctx);  // 开始添加路径
CGContextAddStar(ctx, 7, 160, 220, 40);  // 添加一个7角星的路径
CGContextClosePath(ctx);  // 关闭路径
CGContextSetRGBFillColor(ctx, 0, 1, 0, 1);  // 设置填充颜色
CGContextFillPath(ctx);  // 填充路径
CGContextBeginPath(ctx);  // 开始添加路径
CGContextAddStar(ctx, 9, 260, 220, 40);  // 添加一个9角星的路径
CGContextClosePath(ctx);  // 关闭路径
CGContextSetRGBFillColor(ctx, 0, 0, 1, 1);  // 设置填充颜色
CGContextFillPath(ctx);  // 填充路径
}
@end


上面程序的第一行粗体字代码导入了FKContext.h文件,接下来的粗体字代码多次调用了CGContextAddRoundRect()函数和CGContextAddStar()函数来添加圆角矩形和多角星。编译、运行该程序,即可看到如图12.9所示的效果。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ios quartz 2d