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

Time:09-01

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.

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

enter image description here

Landscape

enter image description here

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:

  • Related