Quartz 2D自定义UI控件

2018-03-08  本文已影响32人  沈正方

(一)什么是Quartz 2D?

(二)Quartz 2D可以做的事情?

  1. 绘制图形:线条、三角形、矩形、圆形、弧形等
    • 手势解锁
    • 绘制图表,折线图、柱状图、饼状图
  2. 绘制文字
    • 涂鸦
  3. 绘制和生成图片
    • 涂鸦
  4. 读取和生成PDF
  5. 截图和裁剪
    • 头像裁剪
  6. 自定义UI控件

(三)Quartz 2D在iOS开发中的价值

在开发iOS程序时,iOS提供了UIKit框架让我们可以利用UIKit框架中的各种各样的UI控件去搭建美观的UI界面。比如:UILabel、UIImageView、UIButton。利用这些UI控件我们可以实现一些常见的界面。但是有些时候,有些UI界面极其复杂,用普通的UI控件无法实现,这是可以利用Quartz 2D技术画出我们需要的复杂结构的UI控件。
所以Quartz 2D的最大的价值就是自定义实现我们需要的但是系统没有提供的UI控件

(四)图形上下文(理解成画板)

在Quartz 2D中的重要概念之一就是图形上下文,我们可以理解成画画的画板
图形上下文是CGContextRef类型

(五)图形上下文的作用

  1. 保存绘图信息、绘图状态
  2. 决定绘制的输出目标(绘制到什么地方去?)
    • 输出目标可以是PDF文件、Bitmap、显示器的窗口
  3. 相同的绘图序列,指定不同的Graphics Context,就可将相同的图像绘制到不同的目标上

(六)Graphics Context的类型

(七)自定义UIView

利用Quartz 2D绘制东西到UIView上需要两个前提条件:

  1. 需要一个图形上下文。(保存绘制信息、决定绘到什么地方去)
  2. 该图形上下文跟UIView关联,如此才能将内容绘制到UIIView上去

实现步骤:

  1. 自定义View继承自UIView
  2. 实现系统的drawRect方法
  3. drawRect方法中
    3.1. 获取跟当前View相关联的图形上下文
    3.2. 绘制相应的图形内容
    3.3. 利用图形上下文将绘制的所有内容渲染显示到View上面

(八)drawRect

为什么要实现drawRect方法才能绘图到View上?

drawRect方法在什么时候调用?

(九)Quartz 2D绘图代码的步骤

1. 获得图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();

2. 拼接路径(下面代码是搞一条线段)
CGContextMoveToPoint(ctx, 10, 10);
CGContextAddLineToPoint(ctx, 100, 100);

3. 绘制路径
CGContextStrokePath(ctx); // CGContextFillPath(ctx);

(十)常用拼接路径的函数

新建一个起点
void CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)

添加新的线段到某个点
void CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y)

添加一个矩形
void CGContextAddRect(CGContextRef c, CGRect rect)

添加一个椭圆
void CGContextAddEllipseInRect(CGContextRef context, CGRect rect)

添加一个圆弧
void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y,
CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)

(十一)常用绘制路径的函数

Mode参数决定绘制的模式
void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)

绘制空心路径
void CGContextStrokePath(CGContextRef c)

绘制实心路径
void CGContextFillPath(CGContextRef c)

提示:一般以CGContextDraw、CGContextStroke、CGContextFill开头的函数,都是用来绘制路径的

(十二)上下文状态栈

上下文状态栈是上下文用来存储路径状态的,这里我们通过一个例子来说明上下文状态栈的原理

在view上画一个“十”字,一横为红色,宽度为10,一竖为绿色,宽度20 ???

错误示例:

let context = UIGraphicsGetCurrentContext()

let path = UIBezierPath()
path.move(to: CGPoint(x: 20, y: 100))
path.addLine(to: CGPoint(x: 180, y: 100))

context?.addPath(path.cgPath)

context?.setStrokeColor(UIColor.red.cgColor)
context?.setLineWidth(10)

let path1 = UIBezierPath()
path1.move(to: CGPoint(x: 100, y: 20))
path1.addLine(to: CGPoint(x: 100, y: 180))

context?.addPath(path1.cgPath)

context?.setStrokeColor(UIColor.green.cgColor)
context?.setLineWidth(20)

context?.strokePath()

首先分析一下为什么上面这段代码无法呈现出我们想要的效果

上面的代码首先获取和当前view相关联的上下文

let context = UIGraphicsGetCurrentContext()

然后,画两条需要的线,且分别添加到上下文上

let path = UIBezierPath()
path.move(to: CGPoint(x: 20, y: 100))
path.addLine(to: CGPoint(x: 180, y: 100))

context?.addPath(path.cgPath)

context?.setStrokeColor(UIColor.red.cgColor)
context?.setLineWidth(10)

let path1 = UIBezierPath()
path1.move(to: CGPoint(x: 100, y: 20))
path1.addLine(to: CGPoint(x: 100, y: 180))

context?.addPath(path1.cgPath)

context?.setStrokeColor(UIColor.green.cgColor)
context?.setLineWidth(20)

然后再把画好的两条线渲染到和当前上下文关联的view的layer上

context?.strokePath()

这里呈现的效果是两条颜色宽度都为绿色的线


image.png

为什么是两条绿色的线?是因为上下文的状态会被覆盖,第二次设置的状态会覆盖第一次设置的状态,导致上下文中所有的线都是最后一次设置的路径状态(宽度为20,颜色为绿色)

为了防止覆盖的问题,我们可以先画好一根线,再画第二根线

// 1. 获取当前view相关联的上下文
/*
 * 获取上下文相当于开辟了一块内存空间
 * 该内存空间分为两部分
 *  1. 存储路径
 *  2. 存储路径的状态(UIColor,Width)
 */
let context = UIGraphicsGetCurrentContext()

let path = UIBezierPath()
path.move(to: CGPoint(x: 100, y: 20))
path.addLine(to: CGPoint(x: 100, y: 180))

context?.addPath(path.cgPath)

context?.setStrokeColor(UIColor.red.cgColor)
context?.setLineWidth(10)

// 取出上下文中所有绘制的路径,并把上下文中存储的路径状态应用到所有的路径上,渲染到上下文关联的view的layer上
context?.strokePath()

/************************分割线************************/

let path1 = UIBezierPath()
path1.move(to: CGPoint(x: 20, y: 100))
path1.addLine(to: CGPoint(x: 180, y: 100))

context?.addPath(path1.cgPath)

context?.setStrokeColor(UIColor.green.cgColor)
context?.setLineWidth(20)

context?.strokePath()
image.png

通过上下文状态栈来实现类似效果
首先如果我们想要在一个view上通过Quartz 2D技术画路径(图),我们需要获取和当前view相关联的上下文(context)
获取上下文相当于在内存中开辟了一块存储空间。
该内存空间分为两部分,这里用A、B表示:
A部分. 存储绘制的路径
B部分. 存储路径的状态(路径的颜色、路径的宽度)

let context = UIGraphicsGetCurrentContext()

然后绘制路径,并添加到上下文中。这部分路径会存储在A部分

let path = UIBezierPath()
path.move(to: CGPoint(x: 20, y: 100))
path.addLine(to: CGPoint(x: 180, y: 100))

context?.addPath(path.cgPath)

再设置上下文中第二条路径(竖)的状态(颜色红,宽度为10)。这部分状态会存储在B部分

context?.setStrokeColor(UIColor.red.cgColor)
context?.setLineWidth(10)

我们先保存B部分的状态到上下文状态栈中

/*
 * 保存上下文中存储的状态到上下文状态栈中(栈:先进后出)
 * 保存了:color:red,width:10到上下文状态栈中
 */
context?.saveGState()

然后再设置第一条路径的状态(颜色绿,宽度20),并渲染到当前上下文关联的viewlayer

context?.setStrokeColor(UIColor.green.cgColor)
context?.setLineWidth(20)

context?.strokePath()

再绘制第二条路径,并取出上下文状态栈中保存的第一条路径的状态,并渲染到当前上下文关联的viewlayer

let path1 = UIBezierPath()
path1.move(to: CGPoint(x: 100, y: 20))
path1.addLine(to: CGPoint(x: 100, y: 180))

context?.addPath(path1.cgPath)

/*
 * 从当前上下文的状态栈中,取出栈顶的(最后存入的状态)状态,覆盖上下文中存储的状态
 */
context?.restoreGState()

context?.strokePath()

(十三)上下文的矩阵操作

let context = UIGraphicsGetCurrentContext()

let path = UIBezierPath(ovalIn: CGRect(x: 100, y: 100, width: 200, height: 130))

// 矩阵操作:平移
context?.translateBy(x: 0, y: 200)
// 矩阵操作:缩放
context?.scaleBy(x: 0.8, y: 1.3)
// 矩阵操作:旋转
context?.rotate(by: .pi/6)

// 注意:矩阵操作要在路径添加前进行,否则对所添加的路径无效
context?.addPath(path.cgPath)

context?.strokePath()
矩阵操作前
矩阵操作后

相关代码点我!!!

上一篇下一篇

猜你喜欢

热点阅读