Jetpack составляет Canvas Arch Extra Stroke

Я использую холст в Jetpack Compose, чтобы нарисовать круг, образованный несколькими арками. А чтобы арки лучше смотрелись, я придал колпаку круглую форму.

        style = Stroke(width = chartBarWidth.toPx(),
                 cap = StrokeCap.Round)

Проблема в том, что при использовании этого ограничения хода угол дуги не корректируется с учетом дополнительных градусов, создаваемых StrokeCap. В итоге я получаю перекрывающиеся арки. Как я могу вычислить дополнительные градусы, создаваемые StrokeCap?

1 ответ

Вам нужно вычислить длину окружности в центральной позиции и разделить ее на ширину штриха, чтобы получить угол. Затем добавьте это в 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)
)

Вы можете добавить коэффициент, если хотите увеличить пространство между дугами.

      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)

Редактировать

Математика получения окружности заключается в том, что нам нужно получить центр ширины штриха, чтобы превратить этот круг в линии с закругленными концами и правильно измерить радиус штриха, который составляет половину ширины штриха.

Как вы можете видеть на изображении ниже, мы получаем радиус, в котором красные линии рисуются с шириной 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 в startAngle, чтобы не перекрывать начальную позицию, и удаляем - 2*strokeAngle, чтобы оставить место для дуги только там, где рисуется только синяя линия.

Другие вопросы по тегам