RitchyCZE
RitchyCZE

Reputation: 178

adding text above icon in jetpack compose

Is there any way how I can show a little description above specific icon in Jetpack Compose like in this picture?

enter image description here

Upvotes: 0

Views: 3281

Answers (2)

Thracian
Thracian

Reputation: 66599

It's called speech or tooltip bubble. You can create this or any shape using GenericShape or adding RoundedRect.

Column(
    modifier = Modifier
        .fillMaxSize()
        .padding(10.dp)
) {

    var showToolTip by remember {
        mutableStateOf(false)
    }


    Spacer(modifier = Modifier.height(100.dp))

    val triangleShape = remember {
        GenericShape { size: Size, layoutDirection: LayoutDirection ->
            val width = size.width
            val height = size.height

            lineTo(width / 2, height)
            lineTo(width, 0f)
            lineTo(0f, 0f)
        }
    }

    Box {

        if (showToolTip) {
            Column(modifier = Modifier.offset(y = (-48).dp)) {


                Box(
                    modifier = Modifier
                        .clip(RoundedCornerShape(10.dp))
                        .shadow(2.dp)
                        .background(Color(0xff26A69A))
                        .padding(8.dp),
                ) {
                    Text("Hello World", color = Color.White)
                }


                Box(
                    modifier = Modifier
                        .offset(x = 15.dp)
                        .clip(triangleShape)
                        .width(20.dp)
                        .height(16.dp)
                        .background(Color(0xff26A69A))
                )
            }
        }

        IconButton(
            onClick = { showToolTip = true }
        ) {
            Icon(
                imageVector = Icons.Default.Add,
                contentDescription = "null",
                Modifier
                    .background(Color.Red, CircleShape)
                    .padding(4.dp)
            )
        }
    }
}

If you need shadow or border that must be a single shape you need to build it with GenericShape. You can check my answer out and library i built.

The sample below is simplified version of library, with no Modifier.layout which is essential for setting space reserved for arrow and setting padding correctly instead of creating another Box with Padding

Result

enter image description here

fun getBubbleShape(
    density: Density,
    cornerRadius: Dp,
    arrowWidth: Dp,
    arrowHeight: Dp,
    arrowOffset: Dp
): GenericShape {

    val cornerRadiusPx: Float
    val arrowWidthPx: Float
    val arrowHeightPx: Float
    val arrowOffsetPx: Float

    with(density) {
        cornerRadiusPx = cornerRadius.toPx()
        arrowWidthPx = arrowWidth.toPx()
        arrowHeightPx = arrowHeight.toPx()
        arrowOffsetPx = arrowOffset.toPx()
    }

    return GenericShape { size: Size, layoutDirection: LayoutDirection ->

        val rectBottom = size.height - arrowHeightPx
        this.addRoundRect(
            RoundRect(
                rect = Rect(
                    offset = Offset.Zero,
                    size = Size(size.width, rectBottom)
                ),
                cornerRadius = CornerRadius(cornerRadiusPx, cornerRadiusPx)
            )
        )
        moveTo(arrowOffsetPx, rectBottom)
        lineTo(arrowOffsetPx + arrowWidthPx / 2, size.height)
        lineTo(arrowOffsetPx + arrowWidthPx, rectBottom)

    }
}

Then create a Bubble Composable, i set static values but you can set these as parameters

@Composable
private fun Bubble(
    modifier: Modifier = Modifier,
    text: String
) {
    val density = LocalDensity.current
    val arrowHeight = 16.dp

    val bubbleShape = remember {
        getBubbleShape(
            density = density,
            cornerRadius = 12.dp,
            arrowWidth = 20.dp,
            arrowHeight = arrowHeight,
            arrowOffset = 30.dp
        )
    }

    Box(
        modifier = modifier
            .clip(bubbleShape)
            .shadow(2.dp)
            .background(Color(0xff26A69A))
            .padding(bottom = arrowHeight),
        contentAlignment = Alignment.Center
    ) {
        Box(modifier = Modifier.padding(8.dp)) {
            Text(
                text = text,
                color = Color.White,
                fontSize = 20.sp
            )
        }
    }
}

You can use it as in this sample. You need to change offset of Bubble to match position of ImageButton

Column(
    modifier = Modifier
        .fillMaxSize()
        .padding(10.dp)
) {

    var showToolTip by remember {
        mutableStateOf(false)
    }


    Spacer(modifier = Modifier.height(100.dp))

    Box {

        if (showToolTip) {
            Bubble(
                modifier = Modifier.offset(x = (-15).dp, (-52).dp),
                text = "Hello World"
            )
        }

        IconButton(
            onClick = { showToolTip = true }
        ) {
            Icon(
                imageVector = Icons.Default.Add,
                contentDescription = "null",
                Modifier
                    .background(Color.Red, CircleShape)
                    .padding(4.dp)
            )
        }
    }
}

Upvotes: 3

A. Hajian
A. Hajian

Reputation: 104

You can use a Box. The children of the Box layout will be stacked over each other.

Box{ 
    Text(text = "Text Above Icon", modifier = text alignment)
    Icon(... , modifier = icon alignment) 
    
}

Upvotes: 0

Related Questions