我一直觉得在Canvas上作画很困难,不仅是在Compose的Canvas上,而且在任何其他技术上也一样。我已经能完成我需要做的任务,但尽量避免使用它来做这些任务。
在一个周日,我想做些有创意的事。我也想编程,所以我决定用Canvas做一个加载指示器。结果事情越做越多,最后我做了一个更像是插画而不是加载指示器的动画。
在这篇和接下来的博客文章中,我将分享我的方法以及我在过程中学到的一些经验。第一篇博客文章将介绍如何在画布上绘制元素,接下来的一篇将讲述动画的相关内容。
想法太空主题插图最初的想法来自于我有一件T恤。就是这件来自Spark公司的I Need Space 女权T恤。我最初的想法是尝试模仿同样的图片,但在画完土星后,我决定自己来。
我决定添加其他元素,并让星星看起来更独特。我对最终效果非常满意!让我们开始绘制插图吧。在下面这个代码片段里可以找到这篇博客文章的[完整代码]。
部件在讨论各个部分之前,我想指出一点:我在这里展示的具体数字只适用于这张特定的图纸,大多数情况下不能泛化。我根据设定的画布尺寸计算了位置和大小,而不是适用于可以调整大小的画布。
第一个组件是背景和画布。Column
作为父组件,Canvas
则是作为子组件。这使我们能够定义一些在Canvas
中无法定义的动画和其他特性。
为了绘制背景,我们将使用父 Column
的 drawBehind
修饰符,绘制一个圆角矩形,并定义一个径向渐变刷,包含一些颜色。
Modifier.drawBehind {
drawRoundRect(
size = size,
cornerRadius = CornerRadius(44f),
brush =
Brush.radialGradient(
colors = listOf(
Color(0xFF02010a),
Color(0xFF04052e),
Color(0xFF140152),
Color(0xFF22007c),
Color.Transparent,
),
radius = size.width * 0.75f,
),
)
},
到这个时候,画看起来是这样的。
你知道吗?土星,你知道它的环是怎么形成的吗?我们首先添加的行星是土星。为了画它,我们需要三个部分:行星的轮廓、边缘和行星内部的线条。我们来定义一个扩展函数drawSaturn
,它接收行星的中心偏移和轮廓样式,然后画出轮廓:
// 绘制土星,给定中心点、偏移量和轮廓样式。
fun DrawScope.drawSaturn(
center: Offset,
outlineStyle: Stroke,
) {
drawCircle(
center = center,
color = Colors.white,
radius = 100f,
style = outlineStyle,
)
}
这是一个圆,它使用了中心坐标和轮廓样式(顺便说一下,这是一个宽度为4f的描边)。我们还为它设置了半径,这里值被硬编码为100f。
接下来,我们画行星的内部线条:
// 绘制一个弧形
drawArc(
topLeft = Offset(
center.x - 80f,
center.y - 80f
),
color = Colors.white,
startAngle = 180f,
sweepAngle = 90f,
useCenter = false,
style =
Stroke(
width = 2f,
),
size = Size(160f, 160f),
)
为此,我们使用drawArc
函数。这个函数与drawCircle
函数有些不同,drawCircle
函数需要中心坐标和半径作为参数。而对于弧形,我们需要定义矩形区域的左上角坐标和弧形的弧度和范围。
startAngle
定义绘制开始的角度,而 sweepAngle
则定义绘制继续的角度范围。角度的0位置在正下方,所以我们从 180f
度开始,并且使用 90f
的弧度角度来实现我们想要的效果。
我们还必须将 useCenter
设为 true
;否则会在中心多画一条线。
最后一个完成Saturn的是轮圈。因为它不是完整的圆形,我们使用drawArc
函数。代码如下所示:
// 旋转40度, 以center为中心点
rotate(40f, center) {
// 开始角度为217度,扫掠角度为285度
drawArc(
color = Colors.white,
startAngle = 217f,
sweepAngle = 285f,
useCenter = false,
topLeft = Offset(
center.x - 50f,
center.y - 150f
),
style = 轮廓样式,
size =
尺寸(100f, 300f),
)
}
需要注意的是,绘制弧线的区域不是正方形——因为轮缘的形状,我们需要使用非正方形尺寸来让其更像椭圆而不是圆。
此时,画大概就像这样等。
星球接下来要添加的是另一颗行星。我不打算给这颗行星命名,而是让它更通用。所以我们就简单地叫它行星好了。
这颗行星有两个组成部分:行星本身和围绕它运转的月亮。我们先来看看行星的轮廓,它是一个圆:
# 假设这里的代码是表示绘制圆形的伪代码,保持原文格式不变。
绘制圆(
中心点 = 中心,
颜色 = Colors白色,
半径 = 80f,
样式 = 边框样式,
)
这段代码很简单;我们以 center
变量定义的位置为中心,画一个半径为 80 的圆。接下来,我们画星球上的线。为此,我们使用三条路径,其中两条是虚线,一条是实线路径。第一条线(最上面的那条)的代码如下所示:
绘制路径(从中心点向左移动82f并沿y轴保持不变,然后通过一个二次贝塞尔曲线移动到中心点位置,颜色为白色,并且样式为虚线,虚线的间隔为60f、10f、50f、10f,线宽为3f)。
其他人的情况差不多;数字和路径效果有些许差异。你可以从这篇博客文章的开头找到完整的代码链接。
所以,我们从圆的边缘向中心画线。因为这条线有点弯曲,我们用quadraticTo
来完成这个效果。
行星的另一部分是月球。首先,它把月球画成一个圆。
画圆(
中心 = 偏移量(
中心.x - 100f,
中心.y + 80f
),
颜色 = 颜色白色,
半径 = 15f,
轮廓样式 = 轮廓样式,
)
然后,对于使月亮看起来像围绕行星运行的线条,我们使用 drawArc
方法:
绘制弧线(
左上角 = 偏移(center.x - 125f, center.y - 125f),
颜色 = Colors.white,
起始角度 = 160f,
扫描角度 = 200f,
使用中心点 = false,
描边 =
描边(
宽度 = 2f,
路径效果 =
路径效果虚线路径效果(
浮点数组 = floatArrayOf(160f, 70f, 50f, 80f, 40f, 40f),
相位 = 0f,
),
),
大小 = 大小(250f, 250f),
)
关于这个弧没有什么新的东西——它使用的是相对于月亮左上角的坐标。从160度开始,弧度延续到200度。为了避开通过中心的第三条线,它没有使用中心点,并且它使用了dashedPathEffect
来产生这种效果。
这时,画看起来是这样。
星星们最终的插图元素是星星部分。让我们为 DrawScope
添加一个扩展函数来画星星,然后用它来画所有的星星。
该函数接受星星的大小、中心偏移、星星的颜色和轮廓样式。我们来定义一下它:
fun DrawScope.drawStar(
starSize: Size,
center: Offset,
color: Color,
outlineStyle: Stroke,
) {
// 绘制星星的函数,参数包括星星的尺寸、中心点、颜色和描边样式。
...
}
我们使用了一条带有二次贝塞尔曲线的路径,从一点到另一点。这个星形有四个角,如果我们把空间想象成一个正方形,那么这些角位于正方形边线的中点。让我们从顶部的中点开始:
val path = Path()
path.moveTo(center.x, center.y - starSize.height * 0.5f) // 将路径移动到中心点的下方,距离为星形大小的一半高度
接下来,我们想从一个点到另一个点绘制一条二次贝塞尔曲线,并使用星形中心作为曲线控制点。从顶部到底部再到右侧的线看起来会是这样:
从中心点向路径添加一个二次贝塞尔曲线,终点的x坐标为星形宽度的一半加上中心点的x坐标,终点的y坐标与中心点相同。
前两个参数是控制点的坐标,后两个参数是线段的终点坐标。其余三条线看起来一样,只是终点坐标基于目标坐标而变化。
画完那四条线之后,我们就可以画路径了。
绘制路径(
路径 (绘制的路径) = path,
颜色 (线条的颜色) = color,
轮廓样式 (线条的样式) = outlineStyle,
)
现在我们有了drawStar
函数,我们可以绘制星星。我们希望把它们放在行星周围,并让它们有不同的颜色。我们还需要记录星星的大小。接下来,我们定义一个数据类来处理这些。
data class 星(
val 大小值: Float,
val 左上角坐标: Offset,
val 颜色值: Color,
)
然后,我们可以定义一个星体列表。它看起来是这样的,列表中有一个星体的例子。
这里定义了一个名为starsList的列表,列表中有一个Star对象,其大小为30f,左上角位于(size.width 0.5f, size.height 0.5f),并且颜色是Colors.stars数组中的第一个颜色。
我们可以定义不同大小的星星,并使用几种不同的颜色。每颗星星的位置是手动计算的。
然后我们可以使用恒星列表,并用该函数把它们画出来。
starsList.forEach { (starSize, offset, color) ->
drawStar(
starSize = Size(starSize, starSize),
color = color,
center = offset,
outlineStyle =
Stroke(
width = 2f,
),
)
}
这些代码更改后,这幅图看起来是这样的:
结尾了在这篇博客文章中,我们使用Canvas创建了一个图。目前还是静态的,但在下一篇文章中,我们将让图中的元素动起来。
你觉得你在使用Canvas方面的技能怎么样?你对自己有信心还是完全不想碰它?
博客文章里的链接