Search code examples
androidkotlinandroid-jetpack-composeandroid-compose-textfieldandroid-jetpack-compose-canvas

How to align centre text in other item of canvas in jetpack compose


I want to align my drawText centre of view in my canvas. I draw my Image view with the help of this answer. Now I want to drawText at above, but my Text is in centre of my view. It's little bit complicated but I'll show you in image.

@OptIn(ExperimentalTextApi::class)
@Composable
fun DrawProgressBar() {
    val rangeComposition = RangeComposition()
    val itemLst = rangeComposition.bpExplained
    val brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue))
    val progressBarPointer = rangeComposition.findReadingWithPointer(142, 90).second
    val textMeasurer = rememberTextMeasurer()
    textMeasurer.measure(text = AnnotatedString("Extremely high"))
    Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .height(60.dp)
            .background(Color.White)
    ) {

        //triangle size

        val rectSize = Size(12.dp.toPx(), 9.dp.toPx())
        val strokeWidth = 8.dp
        val canvasWidth = size.width
        val canvasHeight = size.height
        val strokeWidthPx = density.run { strokeWidth.toPx() }
        val dashedPathEffect =
            PathEffect.dashPathEffect(floatArrayOf(canvasHeight / 38, canvasHeight / 38), 0f)
        val rect = Rect(Offset.Zero, rectSize)
        val trianglePath = Path().apply {
            moveTo(rect.bottomCenter.x, rect.bottomCenter.y)
            lineTo(rect.topRight.x, rect.topRight.y)
            lineTo(rect.topLeft.x, rect.topLeft.y)
            close()
        }
        val progressBarPointerInPixel = (progressBarPointer / 100f) * canvasWidth

        drawIntoCanvas { canvas ->
            // I want to fix this in centre of drawOutline
            drawText(
                textMeasurer = textMeasurer,
                text = "Extremely high",
                topLeft = Offset((progressBarPointerInPixel), 2.dp.toPx()),
                style = TextStyle(fontSize = 12.sp)
            )

            translate(progressBarPointerInPixel, 20.dp.toPx()) {
                canvas.drawOutline(
                    outline = Outline.Generic(trianglePath),
                    paint = Paint().apply {
                        color = Color.DarkGray
                        pathEffect = PathEffect.cornerPathEffect(rect.maxDimension / 3)
                    }
                )
            }

            drawLine(
                start = Offset(x = 0f, y = (canvasHeight / 4) * 3),
                end = Offset(x = canvasWidth, y = (canvasHeight / 4) * 3),
                color = Color.Gray,
                strokeWidth = strokeWidthPx,
                cap = StrokeCap.Round,
            )
            drawLine(
                color = Color.White,
                start = Offset(x = progressBarPointerInPixel, y = (canvasHeight / 4) * 3),
                end = Offset(x = progressBarPointerInPixel + strokeWidthPx / 2, y = (canvasHeight / 4) * 3),
                strokeWidth = strokeWidthPx,
            )
            drawLine(
                brush = brush,
                start = Offset(x = 0f, y = (canvasHeight / 4) * 3),
                end = Offset(x = progressBarPointerInPixel, y = (canvasHeight / 4) * 3),
                strokeWidth = strokeWidthPx,
                cap = StrokeCap.Round,
            )
            drawArc(
                topLeft = Offset(
                    x = progressBarPointerInPixel,
                    y = ((canvasHeight / 4) * 3) - strokeWidthPx / 2
                ),
                size = Size(strokeWidthPx, strokeWidthPx),
                color = Color.White,
                startAngle = -90f,
                sweepAngle = 180f,
                useCenter = true
            )
            itemLst.forEachIndexed { index, rangeItem ->
                val endPointInPixel = (rangeItem.endPoint / 100f) * canvasWidth
                if (index != itemLst.lastIndex) {
                    drawLine(
                        start = Offset(x = endPointInPixel, y = 30.dp.toPx()),
                        end = Offset(x = endPointInPixel, y = canvasHeight),
                        color = Color.Black,
                        strokeWidth = 1.2.dp.toPx(),
                        pathEffect = dashedPathEffect
                    )
                }
            }
        }
    }
}

Actual Output

enter image description here

Expected Output

When text is "Extremely high"

enter image description here

When text is "Very high"

enter image description here


Solution

  • You are using the same horizontal offset for the Text and the triangle.
    The right horizontal offset for the Text is the center of triangle - textWidth/2.

    You can use:

        Canvas(modifier = Modifier.fillMaxSize()){
            
            val rectSize = Size (60f,60f)
            //... your code
    
            val triangleLeftOffset = 150f
    
            drawIntoCanvas { canvas ->
    
                translate (triangleLeftOffset,70f){
                    canvas.drawOutline(
                        outline = Outline.Generic(trianglePath),
                        //...
                    )
                }
            }
    
    
            val textLayoutResult: TextLayoutResult =
                textMeasurer.measure(
                    text = AnnotatedString("Extremely high"),
                    style = TextStyle(color = Color.Blue, fontSize = 20.sp)
                )
            val textSize = textLayoutResult.size
    
            val triangleCenterX = triangleLeftOffset + rectSize.width/2
    
            drawText(
                textLayoutResult = textLayoutResult,
                topLeft = Offset(
                    x  = triangleCenterX - textSize.width/2f,
                    y = 0f
                )
            )
        }
    

    enter image description here