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, чтобы оставить место для дуги только там, где рисуется только синяя линия.