版本记录
版本号 | 时间 |
---|---|
V1.0 | 2021.09.04 星期六 |
前言
quartz
是一个通用的术语,用于描述在iOS
和MAC OS X
中整个媒体层用到的多种技术 包括图形、动画、音频、适配。Quart 2D
是一组二维绘图和渲染API
,Core Graphic
会使用到这组API
,Quartz Core
专指Core Animation
用到的动画相关的库、API
和类。CoreGraphics
是UIKit
下的主要绘图系统,频繁的用于绘制自定义视图。Core Graphics
是高度集成于UIView
和其他UIKit
部分的。Core Graphics
数据结构和函数可以通过前缀CG
来识别。在app中很多时候绘图等操作我们要利用CoreGraphic
框架,它能绘制字符串、图形、渐变色等等,是一个很强大的工具。感兴趣的可以看我另外几篇。
1. CoreGraphic框架解析(一)—— 基本概览
2. CoreGraphic框架解析(二)—— 基本使用
3. CoreGraphic框架解析(三)—— 类波浪线的实现
4. CoreGraphic框架解析(四)—— 基本架构补充
5. CoreGraphic框架解析 (五)—— 基于CoreGraphic的一个简单绘制示例 (一)
6. CoreGraphic框架解析 (六)—— 基于CoreGraphic的一个简单绘制示例 (二)
7. CoreGraphic框架解析 (七)—— 基于CoreGraphic的一个简单绘制示例 (三)
8. CoreGraphic框架解析 (八)—— 基于CoreGraphic的一个简单绘制示例 (四)
9. CoreGraphic框架解析 (九)—— 一个简单小游戏 (一)
10. CoreGraphic框架解析 (十)—— 一个简单小游戏 (二)
11. CoreGraphic框架解析 (十一)—— 一个简单小游戏 (三)
12. CoreGraphic框架解析 (十二)—— Shadows 和 Gloss (一)
13. CoreGraphic框架解析 (十三)—— Shadows 和 Gloss (二)
14. CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)
15. CoreGraphic框架解析 (十五)—— Arcs 和 Paths (二)
16. CoreGraphic框架解析 (十六)—— Lines, Rectangles 和 Gradients (一)
17. CoreGraphic框架解析 (十七)—— Lines, Rectangles 和 Gradients (二)
18. CoreGraphic框架解析 (十八) —— 如何制作Glossy效果的按钮(一)
19. CoreGraphic框架解析 (十九) —— 如何制作Glossy效果的按钮(二)
20. CoreGraphic框架解析 (二十) —— Curves and Layers(一)
21. CoreGraphic框架解析 (二十一) —— Curves and Layers(二)
22. CoreGraphic框架解析 (二十二) —— Gradients 和 Contexts的简单示例(一)
23. CoreGraphic框架解析 (二十三) —— Gradients 和 Contexts的简单示例(二)
24. CoreGraphic框架解析 (二十四) —— 基于Core Graphic的重复图案的绘制(一)
25. CoreGraphic框架解析 (二十五) —— 基于Core Graphic的重复图案的绘制(二)
26. CoreGraphic框架解析 (二十六) —— 以高效的方式绘制图案(一)
27. CoreGraphic框架解析 (二十七) —— 以高效的方式绘制图案(二)
开始
首先看下主要内容:
学习如何使用
Core Graphics
绘制线条、矩形和渐变——从美化table view
开始!内容来自翻译。
下面看下写作环境:
Swift 5.5, iOS 15, Xcode 13
接着就是主要内容了。
Core Graphics
是 iOS 上的一个很酷的 API。 作为开发人员,您可以使用它来自定义具有一些简洁效果的 UI,通常无需艺术家参与。 任何与 2-D
绘图相关的东西——比如绘制形状、填充它们并赋予它们渐变 —— 都是使用 Core Graphics
的好选择。
Core Graphics
的历史可以追溯到 OS X 的早期,它是当今仍在使用的最古老的 API 之一。 也许这就是为什么,对于许多 iOS 开发人员来说,Core Graphics 一开始可能有点令人生畏:它是一个庞大的 API,并且在此过程中会有很多障碍。 然而,从 Swift 3 开始,C 风格的 API 已经更新,看起来和感觉就像你熟悉和喜爱的现代 Swift API!
在本教程中,您将构建一个 Star Wars Top Trumps
卡片应用程序,其中包含一系列星舰:

...以及每艘星舰的详细视图。

在创建此应用程序时,您将学习如何开始使用 Core Graphics
、填充和描边矩形以及绘制线条和渐变以制作自定义table view cells
和背景。
你可能想系好安全带——是时候享受 Core Graphics
的乐趣了!
打开入门项目。打开 Sample Projects
文件夹中的起始项目并环顾四周。具体请看:
-
Starship.swift:在这里,你会找到这个应用程序的主要模型——
Starship
。这是一个简单的结构体,具有Starships
的共同属性。在下面的extension
中,您将找到static
属性all
,它从位于Resources
文件夹中的Starships.json
文件中返回所有星际飞船。 - StarshipsViewController.swift:这是应用程序的主视图控制器。它包含所有星舰的数组并将它们显示在表格中。
- StarshipDetailViewController.swift:此视图控制器显示用户选择的星舰所有字段的表格。
构建并运行应用程序。
注意:如果您没有看到可用于运行项目的模拟器,则您可能使用的是旧版本的
Xcode
。 您可以从 Apple 的 Developer portal门户下载Xcode 13
。

登陆页面是 StarshipsViewController
,显示来自星球大战宇宙的星舰列表。 点击以选择Y-wing
,应用程序将导航到该船的详细视图,其中显示Y-wing
的图像,以及其成本和速度等各种属性。

这是一个功能齐全但很无聊的应用程序。 是时候添加一些金光闪闪了!
Analyzing the Table View Style
在本教程中,您将为两个表格视图添加不同的样式。 更仔细地观察这些变化的样子。
在StarshipsViewController
中,每个单元格:
- 具有从深蓝色到黑色的渐变。
- 以黄色勾勒,从单元格边界
inset
绘制。

在 StarshipDetailViewController
中:
- 列表本身有一个从深蓝色到黑色的渐变。
- 每个单元格都有一个黄色的分隔线,将它与相邻的单元格分开。

要绘制这两种设计,您只需要知道如何使用 Core Graphics
绘制矩形、渐变和线条,这就是您将要学习的内容。
Hello, Core Graphics!
虽然本教程涵盖在 iOS 上使用 Core Graphics
,但重要的是要知道 Core Graphics
可用于所有主要的 Apple 平台,包括: macOS
是 AppKit
; iOS
和 tvOS
是 UIKit
;并通过 WatchKit
在 Apple Watch
上。
你可以把使用 Core Graphics
想象成在物理画布上绘画:绘画操作的顺序很重要。例如,如果您绘制重叠形状,那么您添加的最后一个形状将在顶部并与下面的形状重叠。
Apple
设计了 Core Graphics
,因此作为开发人员,您可以提供有关绘制内容和绘制位置的说明。
Core Graphics Context
,由 CGContext
类表示,定义了 where。您告诉上下文要执行哪些绘图操作。有用于绘制位图图像、绘制到 PDF 文件以及最常见的直接绘制到 UIView
的 CGContexts
。
在这个绘画类比中,Core Graphics Context
代表画家的画布。
Core Graphics Context
是State Machines。例如,当您设置填充颜色时,您就是为整个画布设置了它。您绘制的任何形状都将具有相同的填充颜色,直到您更改它为止。
每个 UIView
都有自己的Core Graphics Context
。要使用 Core Graphics
绘制 UIView
的内容,您必须在视图的 draw(_:)
中编写绘图代码。这是因为 iOS 在调用 draw(_:)
之前立即设置了正确的 CGContext
以绘制到视图中。
现在您了解了如何在 UIKit
中使用 Core Graphics
的基础知识,是时候更新您的应用程序了!
Drawing Rectangles
首先,通过从File
菜单中选择New ▸ File…
来创建一个视图文件。 选择 Cocoa Touch Class
,单击 Next
,然后将类名设置为 StarshipListCellBackground
。 使其成为 UIView
的子类,然后创建类文件。 将以下代码添加到您的新类中:
override func draw(_ rect: CGRect) {
// 1
guard let context = UIGraphicsGetCurrentContext() else {
return
}
// 2
context.setFillColor(UIColor.yellow.cgColor)
// 3
context.fill(bounds)
}
逐行分解:
- 1) 首先,您使用
UIGraphicsGetCurrentContext()
获取此UIView
实例的当前CGContext
。请记住,iOS 会在调用draw(_:)
之前自动为您设置。如果由于任何原因无法获取上下文,则应尽早从该方法返回。 - 2) 然后,您在上下文本身上设置填充颜色。
- 3) 最后,您告诉它填充视图的边界。
如您所见,Core Graphics API
不包含直接绘制填充颜色的形状的方法。相反,有点像向特定画笔添加油漆,您将颜色设置为 CGContext
的状态,然后告诉上下文分别用该颜色绘制什么。
您可能还注意到,当您在上下文中调用 setFillColor(_:)
时,您没有提供标准的 UIColor
。相反,您必须使用 CGColor
,这是 Core Graphics
在内部用来表示颜色的基本数据类型。只需访问任何 UIColor
的 CGColor
属性,即可将 UIColor
转换为 CGColor
非常容易。
1. Showing Your New Cell
要查看您的新视图的情况,请打开 StarshipListCell.swift
。这是一个 UITableViewCell
子类,用于在 StarshipsViewController
中显示星舰。在awakeFromNib
中,在方法末尾添加以下代码:
backgroundView = StarshipListCellBackground()
此代码将单元格的背景视图设置为新视图的背景视图。 构建并运行应用程序,你会在每个单元格中看到一个可爱的黄色背景。

惊人的! 您现在可以使用 Core Graphics
进行绘图。 不管你信不信,你已经学会了很多重要的技巧:如何获取要绘制的上下文、如何更改填充颜色以及如何用颜色填充矩形。 你可以用它制作一些可爱的用户界面。
但是您将更进一步,了解制作出色 UI 的最有价值的技术之一:渐变!
Creating New Colors
你将在这个项目中一次又一次地使用相同的颜色,所以为 UIColor
创建一个扩展,使它们易于访问。 转到 File ▸ New ▸ File...
并创建一个名为 UIColor+Extensions
的新 Swift
文件。 将文件内容替换为以下内容:
import UIKit
extension UIColor {
public static let starwarsYellow =
UIColor(red: 250 / 255, green: 202 / 255, blue: 56 / 255, alpha: 1.0)
public static let starwarsSpaceBlue =
UIColor(red: 5 / 255, green: 10 / 255, blue: 85 / 255, alpha: 1.0)
public static let starwarsStarshipGrey =
UIColor(red: 159 / 255, green: 150 / 255, blue: 135 / 255, alpha: 1.0)
}
此代码定义了三种新颜色,您可以将它们作为 UIColor
上的静态属性进行访问。
Drawing Gradients
接下来,因为你要在这个项目中绘制许多渐变,添加一个绘制它们的辅助方法。 这将通过将梯度代码保存在一个地方并避免重复自己来简化任务。
选择 File ▸ New ▸ File...
并创建一个名为 CGContext+Extensions
的新 Swift
文件。 将文件内容替换为以下内容:
import CoreGraphics
extension CGContext {
func drawLinearGradient(
in rect: CGRect,
startingWith startColor: CGColor,
finishingWith endColor: CGColor
) {
// 1
let colorSpace = CGColorSpaceCreateDeviceRGB()
// 2
let locations: [CGFloat] = [0.0, 1.0]
// 3
let colors = [startColor, endColor] as CFArray
// 4
guard let gradient = CGGradient(
colorsSpace: colorSpace,
colors: colors,
locations: locations
) else {
return
}
}
}
这种方法有很多:
- 1) 首先,设置正确的色彩空间
(color space)
。您可以使用颜色空间做很多事情,但您几乎总是希望使用CGColorSpaceCreateDeviceRGB
来使用标准的设备相关 RGB 颜色空间。 - 2) 接下来,设置一个数组来跟踪渐变范围内每种颜色的位置。值
0
表示渐变开始,1
表示渐变结束。
注意:如果需要,您可以在渐变中使用三种或更多颜色,并且您可以在像这样的数组中设置每种颜色在渐变中的开始位置。这对某些效果很有用。
- 3) 之后,您使用传递给方法的颜色创建一个数组。请注意这里使用的是
CFArray
,而不是Array
,因为您使用的是较低级别的C API
。 - 4) 然后,通过初始化
CGGradient
对象来创建渐变,传入颜色空间、颜色数组和之前创建的位置。如果无论出于何种原因,可选初始化程序失败,您都会提前返回。
你现在有一个渐变参考,但它实际上还没有绘制任何东西——它只是一个指向你稍后实际绘制时将使用的信息的指针。差不多是绘制渐变的时候了,但在此之前,是时候了解更多理论了。
1. The Graphics State Stack
请记住:Core Graphics Contexts
是状态机。 在上下文上设置状态时必须小心,尤其是在传递上下文的函数中,或者在这种情况下,上下文本身的方法,因为在修改上下文之前您无法知道上下文的状态。 考虑 UIView
中的以下代码:
override func draw(_ rect: CGRect) {
// ... get context
context.setFillColor(UIColor.red.cgColor)
drawBlueCircle(in: context)
context.fill(someRect)
}
// ... many lines later
func drawBlueCircle(in context: CGContext) {
context.setFillColor(UIColor.blue.cgColor)
context.addEllipse(in: bounds)
context.drawPath(using: .fill)
}
看一眼这段代码,你可能认为它会在视图中绘制一个红色矩形和一个蓝色圆圈,但你错了! 相反,这段代码绘制了一个蓝色矩形和一个蓝色圆圈——但为什么呢?

因为 drawBlueCircle(in:)
在上下文中设置了蓝色填充颜色,并且因为上下文是一个状态机,所以这会覆盖之前设置的红色填充颜色。
这就是 saveGState()
及其伙伴方法 restoreGState())
的用武之地!
每个 CGContext
都维护一个图形状态堆栈,其中包含当前绘图环境的大部分(尽管不是全部)方面。 saveGState()
将当前状态的副本推送到图形状态堆栈上,然后您可以使用 restoreGState()
将上下文恢复到该状态,并在此过程中从堆栈中删除该状态。
在上面的例子中,你应该像这样修改 drawBlueLines(in:)
:
func drawBlueCircle(in context: CGContext) {
context.saveGState()
context.setFillColor(UIColor.blue.cgColor)
context.addEllipse(in: bounds)
context.drawPath(using: .fill)
context.restoreGState()
}

您可以通过在本教程顶部或底部的下载材料按钮中打开RedBluePlayground.playground
来测试这一点。
2. Completing the Gradient
掌握了图形状态堆栈的知识,是时候完成背景渐变的绘制了。 将以下内容添加到 drawLinearGradient(in:startingWith:finishingWith:)
的末尾:
// 5
let startPoint = CGPoint(x: rect.midX, y: rect.minY)
let endPoint = CGPoint(x: rect.midX, y: rect.maxY)
// 6
saveGState()
// 7
addRect(rect)
clip()
drawLinearGradient(
gradient,
start: startPoint,
end: endPoint,
options: CGGradientDrawingOptions())
restoreGState()
这是该代码的细分:
- 5) 首先计算渐变的起点和终点。您将其设置为从矩形顶部中间到底部中间的一条线。有用的是,
CGRect
包含一些实例属性,例如midX
和maxY
,使这变得非常简单。 - 6) 接下来,因为您将要修改上下文的状态,所以您保存其图形状态并通过恢复它来结束该方法。
- 7) 最后,在提供的矩形中绘制渐变。
drawLinearGradient(_:start:end:options:)
是实际绘制渐变的方法,但除非另有说明,否则它将用渐变填充整个上下文,即您的情况下的整个视图。在这里,您只想填充提供的矩形中的渐变。为此,您需要了解clipping
。
clipping
是 Core Graphics
中的一个很棒的功能,它可以让您将绘图限制为任意形状。您所要做的就是将形状添加到上下文中,但不是像通常那样填充它,而是在上下文上调用 clip()
,然后将所有未来的绘图限制在该区域。
因此,在这种情况下,在最终调用 drawLinearGradient(_:start:end:options:)
绘制渐变之前,在上下文和剪辑上设置提供的矩形。
是时候试试这个方法了!打开 StarshipListCellBackground.swift
并在guard
语句中获取当前 UIGraphicsContext
后,将代码替换为以下内容:
context.drawLinearGradient(
in: bounds,
startingWith: UIColor.starwarsSpaceBlue.cgColor,
finishingWith: UIColor.black.cgColor)
构建并运行

您现在已经为自定义单元格添加了渐变背景。 干得好,然而,公平地说,成品现在看起来并不好看。 是时候用一些标准的 UIKit 主题来解决这个问题了。
Fixing the Theme
打开 StarshipsViewController.swift
。 在 viewDidLoad()
的末尾,添加以下内容:
tableView.separatorStyle = .none
tableView.backgroundColor = .starwarsSpaceBlue
然后,在 tableView(_:cellForRowAt:)
中,就在返回单元格之前,设置文本的颜色:
cell.textLabel?.textColor = .starwarsStarshipGrey
这将移除单元格分隔符并为表格提供一些漂亮的星舰颜色。
接下来,打开 AppDelegate.swift
并在 application(_:didFinishLaunchingWithOptions:)
中,在返回之前添加以下内容:
// Theming
let barAppearance = UINavigationBarAppearance()
barAppearance.configureWithOpaqueBackground()
barAppearance.backgroundColor = .starwarsSpaceBlue
barAppearance.titleTextAttributes = [
.foregroundColor: UIColor.starwarsStarshipGrey
]
UINavigationBar.appearance().tintColor = .starwarsYellow
UINavigationBar.appearance().barStyle = .black
UINavigationBar.appearance().standardAppearance = barAppearance
UINavigationBar.appearance().scrollEdgeAppearance = barAppearance
这将使用 iOS 13
中引入的 UINavigationBarAppearance
类设置导航栏的外观以匹配表格的外观。
构建并运行应用程序。

这样更好!您的星际飞船的table view
开始看起来像太空时代。
Stroking Paths
Core Graphics
中的Stroking
意味着沿着路径绘制一条线,而不是像以前那样填充它。
当 Core Graphics
描边路径时,它会在路径的确切边缘的中间绘制描边线。这可能会导致一些常见问题。
1. Outside the Bounds
首先,如果你在一个矩形的边缘绘制——例如一个边框——Core Graphics
默认不会绘制一半的描边路径。
为什么?因为为 UIView
设置的上下文仅扩展到视图的边界。想象一下在视图边缘使用单点边框进行描边。因为 Core Graphics
沿着路径的中间向下划,线将在视图边界外半点和视图边界内半点。
一个常见的解决方案是在每个方向上将描边矩形的路径inset
为线条宽度的一半,因此它位于视图内。
下图显示了一个黄色矩形,在灰色背景上有一个点宽的红色描边,以一点间隔条纹。在左图中,描边路径遵循视图的边界并已被裁剪。您可以看到这一点,因为红线是灰色方块宽度的一半。在右图中,描边路径inset半个点,现在具有正确的线宽。

2. Anti-Aliasing
其次,您需要了解可能影响边框外观的抗锯齿效果。抗锯齿是渲染引擎用来在显示的图形不能完美映射到设备上的物理像素时避免出现“锯齿状”边缘和线条的技术。
以上一段中视图周围的单点边框为例。如果边框遵循视图的边界,那么 Core Graphics
将尝试在矩形的任一侧绘制一条半点宽的线。
在非 Retina
显示器上,一个点等于设备上的一个像素。不可能只点亮一个像素的一半,因此 Core Graphics
将使用抗锯齿来绘制两个像素,但在较浅的阴影中仅显示单个像素的外观。
在以下几组截图中,左图为非 Retina
显示屏,中间图为scale
为 2
的 Retina
显示屏,第三幅图为scale
为 3 的 Retina 显示屏。
对于第一个图表,请注意 2x 图像如何不显示任何抗锯齿,因为黄色矩形两侧的半点落在像素边界上。但是,在 1x 和 3x 图像中,会出现抗锯齿。

在下一组截图中,描边矩形已inset
半点。 因此,笔划线与点精确对齐,从而与像素边界对齐。 请注意没有锯齿伪影。

Adding a Border
回到您的应用程序! cell
开始看起来不错,但您将添加另一种以使它们脱颖而出。 这一次,您将在单元格边缘绘制一个亮黄色框。
您已经知道如何轻松填充矩形。 好吧,在它们周围描边也很容易。
打开 StarshipListCellBackground.swift
并将以下内容添加到 draw(_:)
的底部:
let strokeRect = bounds.insetBy(dx: 4.5, dy: 4.5)
context.setStrokeColor(UIColor.starwarsYellow.cgColor)
context.setLineWidth(1)
context.stroke(strokeRect)
在这里,您创建一个用于描边的矩形,该矩形在 x 和 y 方向上从背景矩形inset 4.5
个点。 然后,将描边颜色设置为黄色,将线宽设置为 1 磅,最后对矩形进行描边。 构建并运行您的项目。

现在,您的星舰列表看起来像是来自遥远的星系!
Building a Card Layout
虽然 StarshipsViewController
看起来很花哨,但 StarshipDetailViewController
仍然需要一些修饰!

对于此视图,您将首先使用自定义 UITableView
子类在 table view
背景上绘制渐变。
创建一个新的 Cocoa Touch Class
文件,使其成为 UITableView
的子类并命名为 StarshipTableView
。 将以下内容添加到新类:
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {
return
}
context.drawLinearGradient(
in: bounds,
startingWith: UIColor.starwarsSpaceBlue.cgColor,
finishingWith: UIColor.black.cgColor)
}
这应该看起来很熟悉。 在新的table view
子类的 draw(_:)
方法中,您获取当前的 CGContext
,然后在视图的边界中绘制渐变,从顶部的蓝色开始到底部的黑色。 简单的!
打开 Main.storyboard
并单击 Starship Detail View Controller
场景中的 TableView
。 在Identity inspector
中,将类设置为新的 StarshipTableView
。

构建并运行应用程序,然后点击Y-wing
。

你的detail view
现在有一个从上到下的漂亮的全屏渐变,但table view
中的单元格掩盖了效果的最佳部分。 是时候解决这个问题并为细节单元格添加更多风格了。
打开 StarshipDetailViewController.swift
并在 tableView(_:cellForRowAt:)
的底部,在返回item field
的单元格之前,添加以下内容:
cell.textLabel?.textColor = .starwarsStarshipGrey
cell.detailTextLabel?.textColor = .starwarsYellow
cell.backgroundColor = .clear
这只是将单元格的字段名称和值设置为更适合您的星球大战主题的颜色,并将背景颜色设置为clear
。
然后,在tableView(_:cellForRowAt:)
之后,添加以下方法来设置table view header
的样式:
override func tableView(
_ tableView: UITableView,
willDisplayHeaderView view: UIView,
forSection section: Int
) {
view.tintColor = .starwarsYellow
if let header = view as? UITableViewHeaderFooterView {
header.textLabel?.textColor = .starwarsSpaceBlue
}
}
在这里,您将表视图标题视图的色调颜色设置为主题黄色,给它一个黄色背景,并将其文本颜色设置为主题蓝色。
Drawing Lines
作为最后一点,您将在详细信息视图中为每个单元格添加一个splitter
。 打开 StarshipFieldCell.swift
并将以下内容添加到类中:
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {
return
}
let y = bounds.maxY - 0.5
let minX = bounds.minX
let maxX = bounds.maxX
context.setStrokeColor(UIColor.starwarsYellow.cgColor)
context.setLineWidth(1.0)
context.move(to: CGPoint(x: minX, y: y))
context.addLine(to: CGPoint(x: maxX, y: y))
context.strokePath()
}
在这里,您使用 Core Graphics
在单元格边界的底部绘制一条线。 请注意所使用的 y 值如何比视图边界小半点,以确保splitter
完全绘制在单元格内。
现在,您需要实际绘制显示splitter
的线。
要在 A 和 B 之间画一条线,首先要移动到 A 点,这不会导致 Core Graphics
绘制任何内容。 然后向 B 点添加一条线,这会将从 A 点到 B 点的线添加到上下文中。 然后您可以调用 strokePath()
来描边线。
构建并运行您的应用程序。 然后,打开 Y 翼详细视图。 美丽的!

如果本教程有点难以理解,或者您想确保涵盖您的基础知识,请查看 Beginning Core Graphics 视频系列。
如果您正在寻找更高级的东西,请查看中Intermediate Core Graphics课程。
如果您觉得还不能完成整个课程,请尝试 Core Graphics Article Series系列,在那里您将学习如何使用 Core Graphics
从头开始绘制整个应用程序,包括图形!
后记
本篇主要讲述了关于线、矩形和渐变的绘制,感兴趣的给个赞或者关注~~~

网友评论