利用 Jetpack Compose 可更轻松地处理自定义图形。许多应用需要精确控制屏幕上绘制的准确内容。这种控制有可能只是将一个框或圆形放到屏幕的正确位置上,也有可能是精心布置许多不同样式的图形元素。借助 Compose 的声明性方法,所有图形配置都将在一个地方进行,而不必在方法调用和 Paint
辅助对象之间来回切换。Compose 负责高效地创建和更新所需的对象。
使用 Compose 的声明性图形
Compose 将其声明性方法扩展到了处理图形的方式中。Compose 的方法有许多优点:
- Compose 可最大限度地减少图形元素中的状态,有助于避免状态的编程陷阱。
- 当您绘制图形时,所有选项都位于可组合函数中的恰当位置。
- Compose 的图形 API 负责以高效的方式创建和释放对象。
Canvas
自定义图形的核心可组合项是 Canvas
。在布局中放置 Canvas
的方式与放置其他 Compose 界面元素相同。在 Canvas
中,您可以通过精确控制元素的样式和位置来绘制元素。
例如,以下代码将创建一个 Canvas
可组合项,用于填充其父元素中的所有可用空间:
Canvas(modifier = Modifier.fillMaxSize()) {
}
Canvas
会自动提供 DrawScope
(一个维护自身状态且限定了作用域的绘图环境)。这让您可以为一组图形元素设置参数。DrawScope
提供了几个有用的字段,例如 size
,一个指定 DrawScope
的当前尺寸和最大尺寸的 Size
对象。
例如,假设您想绘制一条从画布右上角到左下角的对角线。这可以通过添加 drawLine
可组合项来实现:
Canvas(modifier = Modifier.fillMaxSize()) { val canvasWidth = size.width val canvasHeight = size.height drawLine( start = Offset(x = canvasWidth, y = 0f), end = Offset(x = 0f, y = canvasHeight), color = Color.Blue ) }
图 1. 使用 drawLine
在画布上画一条线。该代码会设置线条的颜色,但会使用默认宽度。
您可以使用其他参数来自定义绘图。例如,默认情况下,线条以细线宽度绘制,无论绘图的比例大小如何,该线条均显示为一个像素宽。您可以通过设置 strokeWidth
值来替换默认值:
Canvas(modifier = Modifier.fillMaxSize()) { val canvasWidth = size.width val canvasHeight = size.height drawLine( start = Offset(x = canvasWidth, y = 0f), end = Offset(x = 0f, y = canvasHeight), color = Color.Blue, strokeWidth = 5F ) }
图 2. 通过替换默认宽度,修改图 1 中的线条。
还有许多其他简单的绘图函数,比如 drawRect
和 drawCircle
。例如,以下代码用于在画布中央绘制一个实心圆,其直径等于画布短边的一半:
Canvas(modifier = Modifier.fillMaxSize()) {
val canvasWidth = size.width
val canvasHeight = size.height
drawCircle(
color = Color.Blue,
center = Offset(x = canvasWidth / 2, y = canvasHeight / 2),
radius = size.minDimension / 4
)
}
图 3. 使用 drawCircle
将圆形放在画布中央。默认情况下,drawCircle
会绘制一个实心圆,因此不必明确指定该设置。
绘图函数有许多有用的默认参数。例如,默认情况下,drawRectangle()
会填充其整个父项范围,drawCircle()
的半径等于其父项短边的一半。跟以往一样,使用 Kotlin 时,您可以通过利用默认参数值并仅设置需要更改的参数,使代码变得更加简单明了。您可以通过为 DrawScope
绘图方法提供显式参数来利用这一点,因为您绘制的元素的默认设置将基于父作用域的设置。
DrawScope
如上所述,每个 Compose Canvas
提供一个 DrawScope
(限定了作用域的绘图环境),您可以在其中实际发出绘图命令。
例如,以下代码用于在画布左上角绘制一个矩形:
Canvas(modifier = Modifier.fillMaxSize()) {
val canvasQuadrantSize = size / 2F
drawRect(
color = Color.Green,
size = canvasQuadrantSize
)
}
您可以使用 DrawScope.inset()
函数来调整当前作用域的默认参数,以更改绘图边界并相应地转换绘图。inset()
之类的操作适用于相应 lambda 中的所有绘图操作:
val canvasQuadrantSize = size / 2F
inset(50F, 30F) {
drawRect(
color = Color.Green,
size = canvasQuadrantSize
)
}
DrawScope
还提供其他简单的转换,例如 rotate()
。例如,以下代码用于在画布中央绘制一个占据九分之一空间的矩形:
val canvasSize = size
val canvasWidth = size.width
val canvasHeight = size.height
drawRect(
color = Color.Gray,
topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
size = canvasSize / 3F
)
图 4. 使用 drawRect
在屏幕中央绘制一个实心矩形。
您可以通过对矩形的 DrawScope
应用旋转来旋转矩形:
rotate(degrees = 45F) {
drawRect(
color = Color.Gray,
topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
size = canvasSize / 3F
)
}
图 5. 我们使用 rotate()
对当前绘图作用域应用旋转,将矩形旋转 45 度。
如需对绘图应用多个转换,最佳方法是不创建嵌套的 DrawScope
环境,而是使用 withTransform()
函数,该函数用于创建并应用整合了您所需的所有更改的单一转换。与对各个转换进行嵌套调用相比,使用 withTransform()
更有效,因为这种情况下所有转换都在单个操作中一起执行,Compose 不必计算并保存每个嵌套转换。
例如,以下代码用于向矩形同时应用平移和旋转:
withTransform({
translate(left = canvasWidth / 5F)
rotate(degrees = 45F)
}) {
drawRect(
color = Color.Gray,
topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
size = canvasSize / 3F
)
}
图 6. 在这里,我们使用 withTransform
同时应用旋转和平移,以便旋转矩形并将其向左移动。