服务器之家

服务器之家 > 正文

iOS开发之Quartz2D的介绍与使用详解

时间:2021-03-10 16:20     来源/作者:李峰峰博客

一、前言

quartz2d的api是纯c语言的,它是一个二维绘图引擎,同时支持ios和mac系统。quartz2d的api来自于core graphics框架,数据类型和函数基本都以cg作为前缀。通常,我们可以使用系统提供的控件去完成大部分ui,但是有些ui界面极其复杂、而且比较个性化,用普通的ui控件无法实现,这时可以利用quartz2d技术将控件内部的结构画出来,类似自定义控件。其实,ios中大部分控件的内容都是通过quartz2d画出来的,因此,quartz2d在ios开发中很重要的一个价值是:自定义view(自定义ui控件)。

quartz 2d能完成的工作:

  1. 绘制图形 : 线条三角形矩形圆弧等;
  2. 绘制文字;
  3. 绘制生成图片(图像);
  4. 读取生成pdf;
  5. 截图裁剪图片;
  6. 自定义ui控件;
  7. … …

二、图形上下文(graphics context)

图形上下文(graphics context):是一个cgcontextref类型的数据。

图形上下文的作用:

(1)保存绘图信息、绘图状态

(2)决定绘制的输出目标(绘制到什么地方去?)

(输出目标可以是pdf文件、bitmap或者显示器的窗口上)

iOS开发之Quartz2D的介绍与使用详解

相同的一套绘图序列,指定不同的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

iOS开发之Quartz2D的介绍与使用详解

将当前的上下文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

运行效果:

iOS开发之Quartz2D的介绍与使用详解

2、核心方法drawrect:

为什么要实现drawrect:方法才能绘图到view上?

因为在drawrect:方法中才能取得跟view相关联的图形上下文

drawrect:方法在什么时候被调用?

当view第一次显示到屏幕上时(被加到uiwindow上显示出来)

调用view的setneedsdisplay或者setneedsdisplayinrect:时.

注意4点:

  1. 手动调用drawrect:方法,不会自动创建跟view相关联的上下文。应该调用setneedsdisplay方法,系统底层会自动调用drawrect,告诉系统重新绘制view.这样,系统底层会自动创建跟view相关联的上下文
  2. setneedsdisplay底层会调用drawrect,并不是立马调用的.只是设了一个调用的标志.调用时刻是等下一次屏幕刷新时才去调用drawrect。屏幕每一秒刷新30-60秒次,所以1秒调用drawrect方法大概30-60次,速度非常快哦
  3. view内部有个layer(图层)属性,drawrect:方法中取得的是一个layer graphics context,因此,绘制的东西其实是绘制到view的layer上去了
  4. 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);// 填充路径
 
}

运行效果:

iOS开发之Quartz2D的介绍与使用详解

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];
 
}

运行效果:

iOS开发之Quartz2D的介绍与使用详解

注:

判断一个点是否在一个矩形框内

?
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);// 填充路径
 
}

运行效果:

iOS开发之Quartz2D的介绍与使用详解

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);// 填充路径
 
}

运行效果:

iOS开发之Quartz2D的介绍与使用详解

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);
 
}

运行效果:

iOS开发之Quartz2D的介绍与使用详解

6、画曲线

本塞尔曲线原理:

iOS开发之Quartz2D的介绍与使用详解

?
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);
 
}

运行效果:

iOS开发之Quartz2D的介绍与使用详解

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];
}

运行效果:

iOS开发之Quartz2D的介绍与使用详解

方法二:

?
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];
 
}

运行效果:

iOS开发之Quartz2D的介绍与使用详解

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

运行效果:

iOS开发之Quartz2D的介绍与使用详解

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

标签:

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
yue是什么意思 网络流行语yue了是什么梗
yue是什么意思 网络流行语yue了是什么梗 2020-10-11
背刺什么意思 网络词语背刺是什么梗
背刺什么意思 网络词语背刺是什么梗 2020-05-22
Intellij idea2020永久破解,亲测可用!!!
Intellij idea2020永久破解,亲测可用!!! 2020-07-29
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总 2020-11-13
返回顶部