Jetpack 撰写 Canvas Arch 额外行程

avr*_*riu 3 android-jetpack-compose android-jetpack-compose-canvas

我在 jetpack compose 中使用画布来绘制由多个拱门形成的圆圈。为了让拱门看起来更好,我将帽子设置为圆形。

  style = Stroke(width = chartBarWidth.toPx(),
                 cap = StrokeCap.Round)
Run Code Online (Sandbox Code Playgroud)

问题是,当使用此行程帽时,拱形角度不会调整以考虑由 StrokeCap 产生的额外角度。所以我最终得到了重叠的拱门。如何计算由 strokeCap 产生的额外度数?在此输入图像描述

Thr*_*ian 8

您需要计算中心位置的圆的周长并将其除以笔划宽度以获得角度。然后将其添加到 startAngle 并从扫描角度删除 2 次,以在删除圆角笔划宽度后获得相同的长度。

在此输入图像描述

val width = size.width
val radius = width / 2f
val strokeWidth = 20.dp.toPx()
            
val circumference = 2 * Math.PI * (radius - strokeWidth / 2)
val strokeAngle = (strokeWidth / circumference * 180f).toFloat()

drawArc(
    color = Color.Blue,
    startAngle = 180f + strokeAngle,
    sweepAngle = 180f - strokeAngle * 2,
    useCenter = false,
    topLeft = Offset(strokeWidth / 2, strokeWidth / 2),
    size = Size(width - strokeWidth, width - strokeWidth),
    style = Stroke(strokeWidth, cap = StrokeCap.Round)

)
            
drawArc(
    color = Color.Red,
    startAngle = 0f + strokeAngle,
    sweepAngle = 180f - strokeAngle * 2,
    useCenter = false,
    topLeft = Offset(strokeWidth / 2, strokeWidth / 2),
    size = Size(width - strokeWidth, width - strokeWidth),
    style = Stroke(strokeWidth, cap = StrokeCap.Round)
)
Run Code Online (Sandbox Code Playgroud)

如果您想增加弧之间的空间,可以添加系数

val strokeAngle = 1.1f * (strokeWidth / circumference * 180f).toFloat()
Run Code Online (Sandbox Code Playgroud)

这是一个示例,其中标签放置在带有圆形连接的圆弧外部

@Preview
@Composable
private fun PieChartWithLabels() {

    Box(
        modifier = Modifier
            .fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        val chartDataList = listOf(
            ChartData(Pink400, 10f),
            ChartData(Orange400, 20f),
            ChartData(Yellow400, 15f),
            ChartData(Green400, 5f),
            ChartData(Blue400, 50f),
        )

        Canvas(
            modifier = Modifier
                .fillMaxWidth(.7f)
                .aspectRatio(1f)
        ) {
            val width = size.width
            val radius = width / 2f
            val strokeWidth = 20.dp.toPx()

            val circumference = 2 * Math.PI * (radius - strokeWidth / 2)
            val strokeAngle = 1.1f * (strokeWidth / circumference * 180f).toFloat()

            var startAngle = -90f

            for (index in 0..chartDataList.lastIndex) {

                val chartData = chartDataList[index]
                val sweepAngle = chartData.data.asAngle
                val angleInRadians = (startAngle + sweepAngle / 2).degreeToAngle

                drawArc(
                    color = chartData.color,
                    startAngle = startAngle + strokeAngle,
                    sweepAngle = sweepAngle - strokeAngle * 2,
                    useCenter = false,
                    topLeft = Offset(strokeWidth / 2, strokeWidth / 2),
                    size = Size(width - strokeWidth, width - strokeWidth),
                    style = Stroke(strokeWidth, cap = StrokeCap.Round)
                )

                startAngle += sweepAngle


                val rectWidth = 20.dp.toPx()
                drawRect(
                    color = Color.Red,
                    size = Size(rectWidth, rectWidth),
                    topLeft = Offset(
                        -rectWidth / 2 + center.x + (radius + strokeWidth) * cos(
                            angleInRadians
                        ),
                        -rectWidth / 2 + center.y + (radius + strokeWidth) * sin(
                            angleInRadians
                        )
                    )
                )
            }
        }
    }
}

private val Float.degreeToAngle
    get() = (this * Math.PI / 180f).toFloat()


@Immutable
data class ChartData(val color: Color, val data: Float)
Run Code Online (Sandbox Code Playgroud)

编辑

获取周长背后的数学是我们需要获取笔划宽度的中心,以将该圆更改为带有圆顶的线,并正确测量笔划半径(笔划宽度的一半)。

在此输入图像描述

正如您在下图中看到的,我们得到了半径,其中红线的宽度是用描边宽度/2绘制的。

@Preview
@Composable
fun CanvasTest() {
    Canvas(
        modifier = Modifier
            .padding(50.dp)
            .fillMaxWidth()
            .aspectRatio(1f)
//            .border(1.dp, Color.Green)
    ) {

        val strokeWidth = 80f

        drawLine(
            color = Color.Blue,
            start = Offset(strokeWidth / 2, strokeWidth / 2),
            end = Offset(200f - strokeWidth / 2, strokeWidth / 2),
            strokeWidth = strokeWidth,
            cap = StrokeCap.Round
        )

        drawArc(
            color = Color.Red,
            useCenter = false,
            topLeft = Offset.Zero,
            size = Size(strokeWidth, strokeWidth),
            startAngle = 90f,
            sweepAngle = 180f,
            style = Stroke(2.dp.toPx())

        )

        drawLine(
            color = Color.Red,
            start = Offset(0f, strokeWidth / 2 + 1),
            end = Offset(strokeWidth / 2, strokeWidth / 2 + 1),
            strokeWidth = 2f
        )

        drawLine(
            color = Color.Red,
            start = Offset(200f - strokeWidth / 2, strokeWidth / 2 + 1),
            end = Offset(200f, strokeWidth / 2 + 1),
            strokeWidth = 2f
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

如果没有圆帽,我们将整体绘制蓝线。使用圆角帽,我们在 startAngle 处偏移为 strokeAngle,以便不与起始位置重叠,并删除 - 2*lines 来为仅绘制蓝线的弧保留空间。