Reputation: 178
Is there any way how I can show a little description above specific icon in Jetpack Compose like in this picture?
Upvotes: 0
Views: 3281
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
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
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