Jetpack Compose 画布架构额外描边。

3
我正在使用Jetpack Compose中的画布来绘制由多个拱形组成的圆。为了让这些拱形看起来更好,我将端点设置为圆形。
  style = Stroke(width = chartBarWidth.toPx(),
                 cap = StrokeCap.Round)

问题是,当使用这个笔触端点时,拱形角度没有调整以考虑笔触端点产生的额外度数。所以我最后得到了重叠的拱形。 我如何计算笔触端点产生的额外度数?enter image description here
1个回答

8
你需要计算圆心位置的周长并将其除以描边宽度以获得角度。然后将此加到起始角度上,并从扫描角度中减去2倍,以在删除圆角描边宽度后获得相同的长度。

enter image description here

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)
)

如果您希望增加弧之间的间距,可以添加系数。

val strokeAngle = 1.1f * (strokeWidth / circumference * 180f).toFloat()

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

@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)

编辑

获取圆周的数学原理是我们需要获取笔画宽度的中心,将这个圆变成带有圆角端点的线条,并正确测量笔画半径,其值为笔画宽度的一半。

enter image description here

如下图所示,我们正在获取半径,其中红线是用strokeWidth/ 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
        )
    }
}

如果没有圆形帽,我们将整个绘制蓝线。有了圆形帽,我们在起始角度处偏移strokeAngle,以避免重叠,并删除-2 * strokeAngle以仅保留弧的空间,其中仅绘制蓝线。


是的,我一开始也考虑过这个问题,但后来担心会被人举报为重复提问并被引用到这篇帖子中。不过,我会另外提一个问题,并附上图片以便视觉学习者像我一样能够理解。 - oop
1
它们相似但并不相同。你的问题需要在一个图层中绘制两个物体,这样橙色只会在与粉色弧线相交的地方绘制出来。 - Thracian
你的问题变得更加复杂,每个弧之间有空隙。如果没有空隙,就像我上面提到的那样,它会很容易解决。 - Thracian
1
我认为没有简单的解决方法。在我看来,你需要使用quadTo和cubicTo来画。 - Thracian
@Thracian,你能帮我一下吗? - avram andrei tiberiu
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接