一、前言
quartz2d的api是纯c语言的,它是一个二维绘图引擎,同时支持ios和mac系统。quartz2d的api来自于core graphics框架,数据类型和函数基本都以cg作为前缀。通常,我们可以使用系统提供的控件去完成大部分ui,但是有些ui界面极其复杂、而且比较个性化,用普通的ui控件无法实现,这时可以利用quartz2d技术将控件内部的结构画出来,类似自定义控件。其实,ios中大部分控件的内容都是通过quartz2d画出来的,因此,quartz2d在ios开发中很重要的一个价值是:自定义view(自定义ui控件)。
quartz 2d能完成的工作:
- 绘制图形 : 线条三角形矩形圆弧等;
- 绘制文字;
- 绘制生成图片(图像);
- 读取生成pdf;
- 截图裁剪图片;
- 自定义ui控件;
- … …
二、图形上下文(graphics context)
图形上下文(graphics context):是一个cgcontextref类型的数据。
图形上下文的作用:
(1)保存绘图信息、绘图状态
(2)决定绘制的输出目标(绘制到什么地方去?)
(输出目标可以是pdf文件、bitmap或者显示器的窗口上)
相同的一套绘图序列,指定不同的graphics context,就可将相同的图像绘制到不同的目标上。
quartz2d提供了以下几种类型的graphics context:
(1)bitmap graphics context
(2)pdf graphics context
(3)window graphics context
(4)layer graphics context
(5)printer graphics context
将当前的上下文copy一份,保存到栈顶(那个栈叫做”图形上下文栈”):
1
|
void cgcontextsavegstate(cgcontextref c)
|
将栈顶的上下文出栈,替换掉当前的上下文(清空之前对于上下文设置):
1
|
void cgcontextrestoregstate(cgcontextref c)
|
三、使用quartz2d自定义view
1、quartz2d自定义view
如何利用quartz2d自定义view?(自定义ui控件)如何利用quartz2d绘制东西到view上?
首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去。
其次,那个图形上下文必须跟view相关联,才能将内容绘制到view上面。
自定义view的步骤:
(1)新建一个类,继承自uiview
(2)实现- (void)drawrect:(cgrect)rect
方法,然后在这个方法中
(a)取得跟当前view相关联的图形上下文
1
|
cgcontextref ctx = uigraphicsgetcurrentcontext();
|
(b)绘制相应的图形内容
例如:画1/4圆(扇形)
1
2
3
4
5
6
7
8
9
10
|
cgcontextmovetopoint(ctx, 100, 100);
cgcontextaddlinetopoint(ctx, 100, 150);
cgcontextaddarc(ctx, 100, 100, 50, -m_pi_2, m_pi, 1);
cgcontextclosepath(ctx);
[[uicolor redcolor] set];
|
(3)利用图形上下文将绘制的所有内容渲染显示到view上面
1
|
cgcontextfillpath(ctx);
|
注:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//m_pi的含义:π
//m_pi * 2的含义:2π
//m_pi_2的含义:π/2
//m_pi / 2的含义:π/2
// 画的图形路径
//bezierpathwitharccenter:弧所在的圆心
//radius:圆的半径
//startangle:开始角度,圆的最右侧为0度
//endangle:截至角度,向下为正,向上为负.
//clockwise:时针的方向,yes:顺时针 no:逆时针
uibezierpath *path = [uibezierpath bezierpathwitharccenter:self.center radius:radius startangle:starta endangle:enda clockwise:no];
|
完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
//
// myview.m
// quartz2dtest
//
// created by 李峰峰 on 2017/2/6.
// copyright © 2017年 李峰峰. all rights reserved.
//
#import "myview.h"
@implementation myview
- (instancetype)initwithframe:(cgrect)frame
{
self = [super initwithframe:frame];
if (self) {
self.backgroundcolor = [uicolor whitecolor]; //设置背景为白色,为了便于观察绘制后的效果
}
return self;
}
- ( void )drawrect:(cgrect)rect {
cgcontextref ctx = uigraphicsgetcurrentcontext();
cgcontextmovetopoint(ctx, 100, 100);
cgcontextaddlinetopoint(ctx, 100, 150);
cgcontextaddarc(ctx, 100, 100, 50, -m_pi_2, m_pi, 1);
cgcontextclosepath(ctx);
[[uicolor redcolor] set];
cgcontextfillpath(ctx);
}
@end
|
运行效果:
2、核心方法drawrect:
为什么要实现drawrect:方法才能绘图到view上?
因为在drawrect:方法中才能取得跟view相关联的图形上下文
drawrect:方法在什么时候被调用?
当view第一次显示到屏幕上时(被加到uiwindow上显示出来)
调用view的setneedsdisplay或者setneedsdisplayinrect:时.
注意4点:
- 手动调用drawrect:方法,不会自动创建跟view相关联的上下文。应该调用setneedsdisplay方法,系统底层会自动调用drawrect,告诉系统重新绘制view.这样,系统底层会自动创建跟view相关联的上下文
- setneedsdisplay底层会调用drawrect,并不是立马调用的.只是设了一个调用的标志.调用时刻是等下一次屏幕刷新时才去调用drawrect。屏幕每一秒刷新30-60秒次,所以1秒调用drawrect方法大概30-60次,速度非常快哦
- view内部有个layer(图层)属性,drawrect:方法中取得的是一个layer graphics context,因此,绘制的东西其实是绘制到view的layer上去了
- view之所以能显示东西,完全是因为它内部的layer
3、quartz2d绘图的代码步骤
第一步:获得图形上下文:
1
|
cgcontextref ctx = uigraphicsgetcurrentcontext();
|
第二步:拼接路径(下面代码是绘制一条线段):
1
2
|
cgcontextmovetopoint(ctx, 10, 10);
cgcontextaddlinetopoint(ctx, 100, 100);
|
第三步:绘制路径:
1
|
cgcontextstrokepath(ctx); // cgcontextfillpath(ctx);
|
四、quartz2d重要函数
1、常用拼接路径函数
新建一个起点
1
|
void cgcontextmovetopoint(cgcontextref c, cgfloat x, cgfloat y)
|
添加新的线段到某个点
1
|
void cgcontextaddlinetopoint(cgcontextref c, cgfloat x, cgfloat y)
|
添加一个矩形
1
|
void cgcontextaddrect(cgcontextref c, cgrect rect)
|
添加一个椭圆
1
|
void cgcontextaddellipseinrect(cgcontextref context, cgrect rect)
|
添加一个圆弧
1
2
3
|
void cgcontextaddarc(cgcontextref c, cgfloat x, cgfloat y,
cgfloat radius, cgfloat startangle, cgfloat endangle, int clockwise)
|
2、常用绘制路径函数
一般以cgcontextdraw、cgcontextstroke、cgcontextfill开头的函数,都是用来绘制路径的。
mode参数决定绘制的模式
1
|
void cgcontextdrawpath(cgcontextref c, cgpathdrawingmode mode)
|
绘制空心路径
1
|
void cgcontextstrokepath(cgcontextref c)
|
绘制实心路径
1
|
void cgcontextfillpath(cgcontextref c)
|
3、矩阵操作函数
利用矩阵操作,能让绘制到上下文中的所有路径一起发生变化。
缩放:
1
|
void cgcontextscalectm(cgcontextref c, cgfloat sx, cgfloat sy)
|
旋转:
1
|
void cgcontextrotatectm(cgcontextref c, cgfloat angle)
|
平移:
1
|
void cgcontexttranslatectm(cgcontextref c, cgfloat tx, cgfloat ty)
|
4、其他常用函数
设置线段宽度
1
|
cgcontextsetlinewidth(ctx, 10);
|
设置线段头尾部的样式
1
|
cgcontextsetlinecap(ctx, kcglinecapround);
|
设置线段转折点的样式
1
|
cgcontextsetlinejoin(ctx, kcglinejoinround);
|
设置颜色
1
|
cgcontextsetrgbstrokecolor(ctx, 1, 0, 0, 1);
|
五、quartz2d的内存管理
关于quartz2d内存管理,有以下原则:
(1)使用含有“create”或“copy”的函数创建的对象,使用完后必须释放,否则将导致内存泄露。
(2)使用不含有“create”或“copy”的函数获取的对象,则不需要释放
(3)如果retain了一个对象,不再使用时,需要将其release掉。
(4)可以使用quartz 2d的函数来指定retain和release一个对象。例如,如果创建了一个cgcolorspace对象,则使用函数cgcolorspaceretain和cgcolorspacerelease来retain和release对象。
(5)也可以使用core foundation的cfretain和cfrelease。注意不能传递null值给这些函数。
六、quartz2d使用案例
1、画矩形、正方形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
- ( void )drawrect:(cgrect)rect {
//1.获取上下文
cgcontextref ctx = uigraphicsgetcurrentcontext();
//2.描述路径
uibezierpath *path = [uibezierpath bezierpathwithrect:cgrectmake(50, 50, 200, 200)];
//3.把路径添加到上下文
cgcontextaddpath(ctx, path.cgpath);
[[uicolor redcolor] set]; // 路径的颜色
//4.把上下文的内容渲染到view的layer.
//cgcontextstrokepath(ctx);// 描边路径
cgcontextfillpath(ctx); // 填充路径
}
|
运行效果:
2、画扇形
除上面“二、使用quartz2d自定义view”中的方法外,也可以使用oc中自带画图方法实现,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
- ( void )drawrect:(cgrect)rect {
cgpoint center = cgpointmake(rect.size.width * 0.5, rect.size.height * 0.5);
cgfloat radius = rect.size.width * 0.5 - 10;
cgfloat starta = 0;
cgfloat enda = -m_pi_2;
// 画弧的路径
uibezierpath *path = [uibezierpath bezierpathwitharccenter:center radius:radius startangle:starta endangle:enda clockwise:no];
// 添加一根线到圆心
[path addlinetopoint:center];
// 闭合路径
[path closepath];
// 路径颜色
[[uicolor redcolor] set];
// 填充路径
[path fill];
// 描边路径
//[path stroke];
}
|
运行效果:
注:
判断一个点是否在一个矩形框内
1
|
cgrectcontainspoint(rect,point); //判断point这个点是否在rect这个矩形框内
|
3、画圆形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
- ( void )drawrect:(cgrect)rect {
//1.获取上下文
cgcontextref ctx = uigraphicsgetcurrentcontext();
//2.描述路径
// cornerradius:圆角半径。矩形的宽高都为200,如果圆角为100,那么两个角之间弧线上任意一点到矩形中心的距离都为100,所以为圆形
uibezierpath *path = [uibezierpath bezierpathwithroundedrect:cgrectmake(50, 50, 200, 200) cornerradius:100];
//3.把路径添加到上下文
cgcontextaddpath(ctx, path.cgpath);
[[uicolor redcolor] set]; // 路径的颜色
//4.把上下文的内容渲染到view的layer.
// cgcontextstrokepath(ctx);// 描边路径
cgcontextfillpath(ctx); // 填充路径
}
|
运行效果:
4、画圆角矩形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
- ( void )drawrect:(cgrect)rect {
//1.获取上下文
cgcontextref ctx = uigraphicsgetcurrentcontext();
//2.描述路径
// cornerradius:圆角半径。
uibezierpath *path = [uibezierpath bezierpathwithroundedrect:cgrectmake(50, 50, 200, 200) cornerradius:50];
//3.把路径添加到上下文
cgcontextaddpath(ctx, path.cgpath);
[[uicolor redcolor] set]; // 路径的颜色
//4.把上下文的内容渲染到view的layer.
cgcontextstrokepath(ctx); // 描边路径
//cgcontextfillpath(ctx);// 填充路径
}
|
运行效果:
5、画直线
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
- ( void )drawrect:(cgrect)rect {
//1.获取跟view相关联的上下文(uigraphics开头)
cgcontextref ctx = uigraphicsgetcurrentcontext();
//2.描述路径
//一条路径可以绘制多条线 路径:path 路径绘制多条线:path使用了两次(2次的起点到终点),都是将线添加到某个点
uibezierpath *path = [uibezierpath bezierpath];
//设置起点
[path movetopoint:cgpointmake(50, 150)];
//添加一根线line到某个点
[path addlinetopoint:cgpointmake(250, 50)];
//画第二根线
[path movetopoint:cgpointmake(50, 250)];
[path addlinetopoint:cgpointmake(250, 100)];
//设置线宽
cgcontextsetlinewidth(ctx, 20);
//设置线的连接样式
cgcontextsetlinejoin(ctx, kcglinejoinbevel);
//设置线的顶角样式
cgcontextsetlinecap(ctx, kcglinecapround); // 圆角线条
//设置线条颜色
[[uicolor redcolor] set];
//3.把路径添加到上下文
cgcontextaddpath(ctx, path.cgpath);
//4.把上下文当中绘制的内容渲染到跟view关联的layer
cgcontextstrokepath(ctx);
}
|
运行效果:
6、画曲线
本塞尔曲线原理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
- ( void )drawrect:(cgrect)rect {
//1.获取跟view相关联的上下文.】
cgcontextref ctx = uigraphicsgetcurrentcontext();
//2.描述路径
uibezierpath *path = [uibezierpath bezierpath];
//画曲线,设置起点.还有一个控制点(用来控制曲线的方向跟弯曲程度)
//设置起点
[path movetopoint:cgpointmake(10, 150)];
//添加一要曲线到某个点
[path addquadcurvetopoint:cgpointmake(200, 150) controlpoint:cgpointmake(150, 10)];
//3.把路径添加到上下文当中
cgcontextaddpath(ctx, path.cgpath);
//4.把上下文的内容渲染view上
cgcontextstrokepath(ctx);
}
|
运行效果:
7、画饼图
方法1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
- ( void )drawrect:(cgrect)rect {
nsarray *dataarray = @[@25,@25,@50];
// 画弧
cgpoint center = cgpointmake(rect.size.width * 0.5, rect.size.height * 0.5);
// 半径
cgfloat radius = rect.size.width * 0.5 - 10;
cgfloat starta = 0;
cgfloat angle = 0;
cgfloat enda = 0;
for (nsnumber *num in dataarray) {
starta = enda;
// 遍历出第一个对象25,angle =25/100 *2π,即angle = π/2,所以为1/4圆,
angle = num.intvalue / 100.0 * m_pi * 2;
// 截至角度= 开始的角度+ 遍历出的对象所占整个圆形的角度
enda = starta + angle;
// 顺势针画贝塞尔曲线
uibezierpath *path = [uibezierpath bezierpathwitharccenter:center radius:radius startangle:starta endangle:enda clockwise:yes];
// 设置随机颜色
[[self randomcolor] set];
// 添加一根线到圆心
[path addlinetopoint:center];
// 填充路径
[path fill];
// 描边路径
// [path stroke];
}
}
/**
生成随机颜色
@return uicolor
*/
-(uicolor *)randomcolor{
cgfloat redlevel = rand () / ( float ) rand_max;
cgfloat greenlevel = rand () / ( float ) rand_max;
cgfloat bluelevel = rand () / ( float ) rand_max;
return [uicolor colorwithred: redlevel green: greenlevel blue: bluelevel alpha: 1.0];
}
|
运行效果:
方法二:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
- ( void )drawrect:(cgrect)rect {
cgpoint center = cgpointmake(self.bounds.size.width * 0.5, self.bounds.size.height * .5);
cgfloat radius = self.bounds.size.width * 0.5 - 10;
cgfloat starta = 0;
cgfloat enda = 25 / 100.0 * m_pi * 2;
uibezierpath *path = [uibezierpath bezierpathwitharccenter:center radius:radius startangle:starta endangle:enda clockwise:yes];
[[uicolor redcolor] set];
//添加一根线到圆心
[path addlinetopoint:center];
[path fill];
//第二个扇形
starta = enda;
cgfloat angle = 25 / 100.0 * m_pi * 2;
enda = starta + angle;
uibezierpath *path2 = [uibezierpath bezierpathwitharccenter:center radius:radius startangle:starta endangle:enda clockwise:yes];
[[uicolor greencolor] set];
//添加一根线到圆心
[path2 addlinetopoint:center];
[path2 fill];
starta = enda;
angle = 50 / 100.0 * m_pi * 2;
enda = starta + angle;
uibezierpath *path3 = [uibezierpath bezierpathwitharccenter:center radius:radius startangle:starta endangle:enda clockwise:yes];
[[uicolor bluecolor] set];
//添加一根线到圆心
[path3 addlinetopoint:center];
[path3 fill];
}
/**
生成随机颜色
@return uicolor
*/
-(uicolor *)randomcolor{
cgfloat redlevel = rand () / ( float ) rand_max;
cgfloat greenlevel = rand () / ( float ) rand_max;
cgfloat bluelevel = rand () / ( float ) rand_max;
return [uicolor colorwithred: redlevel green: greenlevel blue: bluelevel alpha: 1.0];
}
|
注:
如果想实现点击以下变换颜色可以加上如下代码:
1
2
3
4
5
6
|
- ( void )touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event {
//重绘
[self setneedsdisplay];
}
|
8、绘制文字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
- ( void )drawrect:(cgrect)rect {
nsstring *str = @ "李峰峰博客:http://www.imlifengfeng.com/" ;
nsmutabledictionary *dict = [nsmutabledictionary dictionary];
//设置字体
dict[nsfontattributename] = [uifont systemfontofsize:30];
//设置颜色
dict[nsforegroundcolorattributename] = [uicolor redcolor];
//设置描边
dict[nsstrokecolorattributename] = [uicolor bluecolor];
dict[nsstrokewidthattributename] = @3;
//设置阴影
nsshadow *shadow = [[nsshadow alloc] init];
shadow.shadowcolor = [uicolor greencolor];
shadow.shadowoffset = cgsizemake(-2, -2);
shadow.shadowblurradius = 3;
dict[nsshadowattributename] = shadow;
//设置文字的属性
//drawatpoint不会自动换行
//[str drawatpoint:cgpointmake(0, 0) withattributes:dict];
//drawinrect会自动换行
[str drawinrect:self.bounds withattributes:dict];
}
|
运行效果:
9、加水印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
//
// viewcontroller.m
// quartz2dtest
//
// created by 李峰峰 on 2017/2/6.
// copyright © 2017年 李峰峰. all rights reserved.
//
#import "viewcontroller.h"
#import "myview.h"
@interface viewcontroller ()
@end
@implementation viewcontroller
- ( void )viewdidload {
[super viewdidload];
uiimageview *myimageview = [[uiimageview alloc]initwithframe:cgrectmake(0, 0, [uiscreen mainscreen].bounds.size.width, [uiscreen mainscreen].bounds.size.height)];
[self.view addsubview:myimageview];
//生成一张图片
//0.加载图片
uiimage *oriimage = [uiimage imagenamed:@ "test" ];
//1.创建位图上下文(size:开启多大的上下文,就会生成多大的图片)
uigraphicsbeginimagecontext(oriimage.size);
//2.把图片绘制到上下文当中
[oriimage drawatpoint:cgpointzero];
//3.绘制水印(虽说uilabel可以快速实现这种效果,但是我们也可以绘制出来)
nsstring *str = @ "李峰峰博客" ;
nsmutabledictionary *dict = [nsmutabledictionary dictionary];
dict[nsfontattributename] = [uifont systemfontofsize:20];
dict[nsforegroundcolorattributename] = [uicolor redcolor];
[str drawatpoint:cgpointzero withattributes:dict];
//4.从上下文当中生成一张图片
uiimage *newimage = uigraphicsgetimagefromcurrentimagecontext();
//5.关闭位图上下文
uigraphicsendimagecontext();
myimageview.image = newimage;
}
@end
|
运行效果:
10、屏幕截图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
-( void )touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event {
//生成图片
//1.开启一个位图上下文
uigraphicsbeginimagecontext(self.view.bounds.size);
//2.把view的内容绘制到上下文当中
cgcontextref ctx = uigraphicsgetcurrentcontext();
//uiview内容想要绘制到上下文当中, 必须使用渲染的方式
[self.view.layer renderincontext:ctx];
//3.从上下文当中生成一张图片
uiimage *newimage = uigraphicsgetimagefromcurrentimagecontext();
//4.关闭上下文
uigraphicsendimagecontext();
//把图片转成二进制流
//nsdata *data = uiimagejpegrepresentation(newimage, 1);
nsdata *data = uiimagepngrepresentation(newimage);
[data writetofile:@ "/users/lifengfeng/downloads/imlifengfeng.jpg" atomically:yes];
}
|
运行效果:
截图而已,就跟普通截图一样,自己试。
总结
以上就是关于quartz2d一些常用的案例,quartz2d还可以实现更多效果,具体的根据需求去实现。希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:http://www.imlifengfeng.com/blog/?p=514