Quartz 提供了两种不透明的数据类型来创建渐变CGShading 和 CGGradient,你可以使用其中任何一个来创建轴向或径向渐变。

轴向渐变:沿着一个轴方向线性渐变

径向渐变:一个点为原型,指定半径的范围内辐射渐变

我们用两个图来理解一下这两个概念:

1、轴向渐变(轴:沿着左上角 到 右下角)

2、径向渐变(以中心点为圆心,向半径为50的四周方向辐射渐变)

CGShading与CGGradient之间的关系,以及使用的不同。

CGGradient是CGShading的子集,CGGradient封装了对CGShading的渐变实现函数,因此CGGradient使用起来会比CGShading简单。

下面介绍一下两者的不同:

CGGradient CGShading
使用同一个CGGradient实例可以同时绘制轴向渐变和径向渐变 在绘制轴向渐变和径向渐变时,需要分别实例化不同的对象
在绘制时创建渐变的几何形状 在创建对象时创建渐变的几何形状
Quartz自动完成计算渐变中每个点的颜色变化 你需要提供一个回调函数,来计算渐变中每个点的颜色
使用简单,定义两个点和对应的颜色即可 需要在回调函数中计算,使用比较麻烦

我们先来介绍一下CGGradient的使用,它使用起来很简单,我们通过它来实现上面介绍的轴向渐变和径向渐变的例子

实例化方法:

// 参数
// 1、颜色空间
// 2、指定渐变中的颜色(需要与locations个数对应,每组颜色值为rgba(根据颜色空间决定),例如:1,0,0,1,表示一个红色,透明度为1)
// 3、指定渐变的过度位置(值范围:[0,1],0:几何开始的位置;1:几何结束的位置;0.5:几何中间的位置)
init?(colorSpace space: CGColorSpace,
colorComponents components: UnsafePointer<CGFloat>,
locations: UnsafePointer<CGFloat>?, count: Int) // 参数
// 1、颜色空间
// 2、指定渐变中的颜色(需要与locations个数对应,每组颜色值为:CGColor)
// 3、指定渐变的过度位置(值范围:[0,1],0:几何开始的位置;1:几何结束的位置;0.5:几何中间的位置)
init?(colorsSpace space: CGColorSpace?, colors: CFArray, locations: UnsafePointer<CGFloat>?)

具体实现代码:

override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return } // 初始化颜色空间,有两种方法,一般采用第一种(下面两个颜色空间是一样的)
// 方法1:
let space = CGColorSpaceCreateDeviceRGB()
// 方法2:
// let space = CGColorSpace(name: CGColorSpace.sRGB)!
// 设置渐变的过度位置,范围:[0, 1]
var locations: [CGFloat] = [0, 0.5, 1]
// 设置过度的颜色组件集合,因为我们的颜色空间是RGB,因此每组的颜色值为rgba,即:[r, g, b, a]
// 注意:每组的颜色是在数组中连续的,即:[r,g,b,a, r,g,b,a, r,g,b,a]
var colors: [CGFloat] = [1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1]
// 初始化渐变对象CGGradient
guard let gradient = CGGradient(colorSpace: space,
colorComponents: &colors,
locations: &locations,
count: locations.count) else {
print("gradient create fail")
return
}
// 绘制轴向渐变
// 参数:
// 1、渐变对象实例
// 2、渐变的几何开始点坐标
// 3、渐变的几何结束点坐标
// 4、绘制选项,包括:
// (1)drawsBeforeStartLocation:在几何开始点的位置之前的空间部分(超出边界不绘制),用第一个颜色值扩散颜色
// (2)drawsAfterEndLocation:在几何结束点的位置之后的空间部分(超出边界不绘制),用最后一个颜色值扩散颜色
context.drawLinearGradient(gradient,
start: .init(x: 0, y: 0),
end: .init(x: bounds.width, y: bounds.height),
options: .drawsBeforeStartLocation) // 绘制径向渐变
// 参数:
// 1、渐变对象实例
// 2、渐变开始的圆心点坐标
// 3、渐变开始的圆半径(辐射半径)
// 4、渐变结束的圆心点坐标
// 5、渐变结束的圆半径(辐射半径)
// 6、绘制选项,跟上面的一样
// context.drawRadialGradient(gradient,
// startCenter: .init(x:bounds.width/2, y:bounds.height/2),
// startRadius: 10,
// endCenter: .init(x: bounds.width/2, y: bounds.height/2),
// endRadius: 50,
// options: .drawsBeforeStartLocation) }

代码中注释介绍的已经很详细了,这里我给大家着重介绍一下options参数。我们看下图便一目了然:

红色区域:是渐变开始点绘制之前的颜色填充部分。

蓝色区域:是渐变结束点绘制之后的颜色填充部分。

options选项决定在渐变的开始点之前 还是 结束点之后 的区域内进行颜色填,填充的颜色色值为:

1、开始点之前绘制(drawsBeforeStartLocation):填充色为颜色数组中第一个颜色值

2、结束点之后绘制(drawsAfterEndLocation):填充色为颜色数组中最后一个颜色值

下面是两种options的轴向渐变的绘图结果:(径向渐变也是如此)

drawsBeforeStartLocation:(开始点前采用红色填充,结束点后无填充)

drawsAfterEndLocation:(开始点前无填充,结束点后采用蓝色填充)

接下来介绍CGShading的使用,它在绘制渐变时用起来比CGGradient要复杂,因为需要我们自己来计算渐变过程中每个点的颜色。使用时涉及到以下几个关键对象:

CGFunctionCallbacks:用于提供计算渐变过程中每个点的颜色的回调函数,这就是我们实现渐变的关键点。

CGFunction:为上面的回调函数设置初始值,以及相关值的范围的对象。

CGShading:定义渐变的规则,例如:颜色空间、从几何区域的哪里开始渐变到哪里结束渐变等。

实现轴向渐变与径向渐变的实例化方法不一样,下面是对应这两种实现的实例化方法:

// 初始化轴向渐变对象
init?(axialSpace space: CGColorSpace,
start: CGPoint,
end: CGPoint,
function: CGFunction,
extendStart: Bool,
extendEnd: Bool) // 初始化径向渐变对象
init?(radialSpace space: CGColorSpace,
start: CGPoint,
startRadius: CGFloat,
end: CGPoint,
endRadius: CGFloat,
function: CGFunction,
extendStart: Bool,
extendEnd: Bool)

下面是一个利用CGShading实现轴向渐变的具体实现的demo,代码中注释写的很详细,大家可以参考。

override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return } // 初始化一个回调函数,用于计算每个点的颜色值
// 参数:
// 1、CGFunction初始化传入的第一个参数info(指针类型)
// 2、输入值
// 3、输出值
// 4、释放info指针的回调函数
var callback = CGFunctionCallbacks(version: 0) { info, inVal, outVal in let numberOfComponents: Int
if let info = info {
// 注意这里不能用takeRetainedValue()取值,否则会提前释放info的内存,导致下次访问info时出错
numberOfComponents = Unmanaged<NSNumber>.fromOpaque(info).takeUnretainedValue().intValue
}else{
numberOfComponents = 0
} // 渐变开始的颜色值
let startColor: [CGFloat] = [1, 0, 0, 1]
// 渐变结束的颜色值
let endColor: [CGFloat] = [0, 1, 0, 1] let aInVal = inVal.pointee // 获取每次迭代的输入值,下面指定了输入值范围在[0, 1],因此aInVal的值范围在[0, 1]
var out = outVal // 下面指定了输出值包含的分量个数为numberOfComponents(即:out中应返回numberOfComponents个元素)
for i in 0..<numberOfComponents { // 注意:下面指定了输出值中每个分量(每个元素)的值范围[0, 1],因此这里的计算结果不要超出这个范围
if aInVal < 0.5 {
// 计算渐变开始点到中间点区间的颜色分量值
out.pointee = startColor[i] * (0.8 - aInVal)
}else{
// 计算渐变中间点到结束点区间的颜色分量值
out.pointee = endColor[i] * (aInVal - 0.2)
}
out = out.successor()
} } releaseInfo: { info in
guard let info = info else { return }
// 释放info指针内存
Unmanaged<NSNumber>.fromOpaque(info).release()
} // 初始化颜色空间
let space = CGColorSpaceCreateDeviceRGB() // 获取通道颜色分量的个数,这里要加一个alpha
let numberOfComponents = space.numberOfComponents + 1 // rgb + a
// 指定上面callback中输入值的递增迭代次数,这里设置1,即:输入值会从domain的范围内,迭代一次(0, 0.0.00167, ..., 1)终止
// 如果设置为2,会迭代两次:第一次(0, 0.0.00167, ..., 1),第二次(0, 0.0.00167, ..., 1)
let domainDimension = 1
// 输入值的变化范围,这里指定从0到1,即输入值会根据渐变的开始点到结束点的变化,从0开始递增到1,如(0, 0.0.00167, ..., 1)
// 如果设置为[1, 10],则输入值会从1开始递增到10
let domain: [CGFloat] = [0, 1] // 注意:这里的范围必须是递增的
// 指定上面callback中输出值包含的分量个数,如果为4,那么上面callback的输出值out指针应该包含4个分量值(即:rgba)。
// 可以理解为输出值数组的size
let rangeDimension = numberOfComponents
// 输出值每个分量的范围,即r值的范围[0, 1],g值的范围[0, 1], b值的范围[0, 1], a值的范围[0, 1]
// 可以理解为输出值数组中每个元素值的取值范围
let range: [CGFloat] = [0, 1, 0, 1, 0, 1, 0, 1] // 数组下标0和1代表第一个值的范围,一次类推
guard let function = CGFunction(info: Unmanaged<NSNumber>.passRetained(NSNumber(integerLiteral: numberOfComponents)).toOpaque(),
domainDimension: domainDimension,
domain: domain,
rangeDimension: rangeDimension,
range: range,
callbacks: &callback) else {
print("function create fail")
return
} // 渐变的开始点坐标
let start = CGPoint(x: bounds.width/4, y: bounds.height/4)
// 渐变的结束点坐标
let end = CGPoint(x: bounds.width * 3 / 4, y: bounds.height * 3 / 4) // 指定渐变开始点之前的区域是否执行填充颜色,相当于使用CGGradient绘制渐变时,调用drawLinearGradient方法时设置的:drawsBeforeStartLocation
let extendStart = true
// 指定渐变结束点之后的区域是否执行填充颜色,相当于使用CGGradient绘制渐变时,调用drawLinearGradient方法时设置的:drawsAfterEndLocation
let extendEnd = true
// 初始化一个轴向渐变实例CGShading
guard let shading = CGShading(axialSpace: space,
start: start,
end: end,
function: function,
extendStart: extendStart,
extendEnd: extendEnd) else {
print("shading create fail")
return
}
// 绘制渐变
context.drawShading(shading)
}

代码中注释介绍的已经很详细了,这里就不详细说明了,下面是实现的效果:

径向渐变的实现也是类似的,只不过是CGShading的实例化方法不一样。下面是采用CGShading实现的径向渐变的效果:

Quartz 2D CGGradient与CGShading实现渐变的绘制的更多相关文章

  1. iOS 2D绘图详解(Quartz 2D)之阴影和渐变(Shadow,Gradient)

    前言:这个系列写道这里已经是第五篇了,本文会介绍下阴影和渐变的基础知识,以及一些基本的Demo Code展示,应该还会有两篇,介绍下Bitmap绘制以及Pattern等知识. Shadow shado ...

  2. iOS - Quartz 2D 二维绘图

    1.Quartz 2D 简介 Quartz 2D 属于 Core Graphics(所以大多数相关方法的都是以 CG 开头),是 iOS/Mac OSX 提供的在内核之上的强大的 2D 绘图引擎,并且 ...

  3. 1.0 Quartz 2D 简介

    本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末 如果觉得本文内容过长,请前往本人 “简书”   Quartz2D须知:   (1)Quartz 2D是苹果官方的二维绘图引擎,同时支持 ...

  4. iPhone之Quartz 2D系列--图形上下文(2)(Graphics Contexts)

    以下几遍关于Quartz 2D博文都是转载自:http://www.cocoachina.com/bbs/u.php?action=topic&uid=38018 iPhone之Quartz ...

  5. Quartz 2D - 图形上下文(Graphics Contexts)

    一个Graphics Context表示一个绘制目标.它包含绘制系统用于完成绘制指令的绘制参数和设备相关信息.Graphics Context定义了基本的绘制属性,如颜色.裁减区域.线条宽度和样式信息 ...

  6. 使用Quartz 2D擦除图片

    Quartz 2D 是一个强大的二位图像绘制引擎,在开发中如果遇到需要高度自定义的控件,我们就可能需要用Core Graphics进行绘制. 这几天一同事开发一个聊天中的一个子模块,A 画一幅图,然后 ...

  7. Quartz 2D编程指南(2) - 图形上下文

    一个Graphics Context表示一个绘制目标.它包含绘制系统用于完成绘制指令的绘制参数和设备相关信息.Graphics Context定义了基本的绘制属性,如颜色.裁减区域.线条宽度和样式信息 ...

  8. iOS 2D绘图 (Quartz 2D) 概述

    本篇博客原文地址:http://blog.csdn.net/hello_hwc?viewmode=list 由于自己的项目需要,从网络上下载了许多关于绘制图形的demo,只是用在自己的项目中,很多地方 ...

  9. iOS 2D绘图详解(Quartz 2D)之概述

    前言:最近在研究自定义控件,由于想要彻底的定制控件的视图还是要继承UIView,虽然对CALayer及其子类很熟练,但是对Quartz 2D这个强大的框架仍然概念模棱两可.于是,决定学习下,暂定7篇文 ...

  10. iPhone之Quartz 2D系列--编程指南(1)概览

    以下几遍关于Quartz 2D博文都是转载自:http://www.cocoachina.com/bbs/u.php?action=topic&uid=38018 iPhone之Quartz ...

随机推荐

  1. 微信小程序更新机制

    微信小程序更新机制的说明 官方文档地址:https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/update-mechan ...

  2. 2021级《JAVA语言程序设计》上机考试试题4

    现在就是写学生,学生查看个人信息,,修改个人密码,学生功能页的页面,代码最一开始给了 然后,这三个比较紧密,所以一起写了 学生功能页 <%@ page language="java&q ...

  3. 什么是Http? http和https的区别

    一.HTTP HTTP (HyperText Transfer Protocol),即超文本运输协议,是实现网络通信的一种规范 在计算机和网络世界有,存在不同的协议,如广播协议.寻址协议.路由协议等等 ...

  4. Ansible-playbook 快速入门到放弃

    Ansible-playbook 快速入门到放弃 隔岸红尘忙似火,当轩青嶂冷如冰. 1-简介 playbook 相当于可以把模块命令都写入到配置文件里面,这样就可以直接执行配置文件了,类似脚本. 2- ...

  5. 代码随想录算法训练营day10 | leetcode 232.用栈实现队列 225. 用队列实现栈

    基础知识 使用ArrayDeque 实现栈和队列 stack push pop peek isEmpty() size() queue offer poll peek isEmpty() size() ...

  6. 使用vscode编辑markdown

    目录 markdown在vscode中的使用 标题 一级标题 二级标题 三级标题 四级标题 五级标题 六级标题 列表 图片 表格 网址 代码 文本样式 引用 目录 vscode中使用的插件推荐 截图工 ...

  7. vue学习笔记:Vue生命周期

    概念 一个Vue 实例从开始创建.初始化数据.编译模板.挂载 DOM.渲染→更新→渲染.卸载等一系列过程,这就是 Vue 的生命周期.同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不 ...

  8. B站【挽救小白第一季】前端代码记录笔记

    1. 新建工程命令: $ vue init mpvue/mpvue-quickstart my-project 参考mpvue官方快速入门:http://mpvue.com/mpvue/quickst ...

  9. Vulnhub:Player-v1.1靶机

    kali:192.168.111.111 靶机:192.168.111.178 信息收集 端口扫描 nmap -A -v -sV -T5 -p- --script=http-enum 192.168. ...

  10. CF1768F 题解

    题意 传送门 给定长度为 \(n\) 的序列 \(a\),求序列 \(f\),满足: \[f_i= \begin{equation} \begin{cases} 0&(i=1)\\ \min\ ...