androidkotlinandroid-jetpack-composeandroid-jetpack-compose-canvas

How to draw a multicolored bar with Canvas in Jetpack Compose?


How to attain a UI like this in Compose?

Stacked bar

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.


Solution

  • 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

    enter image description here

    Landscape

    enter image description here