How to attain a UI like this in Compose?
Suppose there is a list of slices:
data class Slice(val value: Float, val color: Color)
Group-1 is 14.6, Red
Group-2 is 61.8, Blue
Group-3 is 23.6, Green
Total 100.0
Canvas is not necessary anyhow.
CodePudding user response:
Updated your Slice data class to contain text to draw Text too. You can remove it if you don't want to draw text
data class Slice(val value: Float, val color: Color, val text: String = "")
And Canvas to draw rectangles and text
@OptIn(ExperimentalTextApi::class)
@Composable
private fun StackedBar(modifier: Modifier, slices: List<Slice>) {
val textMeasurer = rememberTextMeasurer()
val textLayoutResults = remember {
mutableListOf<TextLayoutResult>().apply {
slices.forEach {
val textLayoutResult: TextLayoutResult =
textMeasurer.measure(
text = AnnotatedString(it.text),
style = TextStyle(
color = Color.White,
fontSize = 18.sp
)
)
add(textLayoutResult)
}
}
}
Canvas(modifier = modifier) {
val canvasWidth = size.width
val canvasHeight = size.height
var currentX = 0f
slices.forEachIndexed { index: Int, slice: Slice ->
val width = (slice.value) / 100f * canvasWidth
// Draw Rectangles
drawRect(
color = slice.color, topLeft = Offset(currentX, 0f), size = Size(
width,
canvasHeight
)
)
// Draw Text
val textSize = textLayoutResults[index].size
val style = textLayoutResults[index].layoutInput.style
drawText(
textMeasurer = textMeasurer, text = slice.text, topLeft = Offset(
x = currentX (width - textSize.height) / 2,
y = (canvasHeight - textSize.height) / 2
),
style = style
)
// Update start position of next rectangle
currentX = width
}
}
}
Usage
Column {
val slices = listOf(
Slice(value = 14.6f, color = Color.Red, text = "55"),
Slice(value = 61.8f, color = Color.Blue, text = "233"),
Slice(value = 23.6f, color = Color.Green, text = "89")
)
StackedBar(
modifier = Modifier
.fillMaxWidth()
.height(100.dp),
slices = slices
)
}
Result
Landscape
CodePudding user response:
If we're not using canvas, we can use floating percentage value as weight
for children of Row
or Column
.
val slices = listOf(
Slice(value = 14.6f, color = Color(0XFFe31a1a), text = "55"),
Slice(value = 61.8f, color = Color(0XFF377eb8), text = "233"),
Slice(value = 23.6f, color = Color(0XFF49a345), text = "89")
)
var state by remember { mutableStateOf(.0f) }
val width = animateFloatAsState(targetValue = state, animationSpec = tween(300))
LaunchedEffect(key1 = Unit, block = {
delay(1000L)
state = 1f
})
Row(
modifier = Modifier
.padding(14.dp)
.height(64.dp)
.fillMaxWidth(),
) {
slices.forEach {
Column(
modifier = Modifier
.fillMaxHeight()
.weight(it.value),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = it.value.toString(),
color = Color.Black,
modifier = Modifier.padding(4.dp)
)
Box(
modifier = Modifier
.fillMaxSize()
.background(it.color),
contentAlignment = Alignment.Center
) {
Text(text = it.text, color = Color.White)
}
}
}
}
Result: