I would like to create a design similar to the picture below.
I looked into the shape in Android Jetpack Compose but not able to find it. I'm not sure what views I should use to create this layout.
I want a box with top right side should be round just like the image.
To create a design like the one in the picture, you can use a custom shape. Here’s how you can do it:
1. Create a custom shape:
val shape = remember {
GenericShape { size, _ ->
val width = size.width
val height = size.height
moveTo(0f, radius)
quadraticBezierTo(0f, 0f, radius, 0f)
lineTo(width - cornerRadius - radius, 0f)
quadraticBezierTo(width - cornerRadius, 0f, width - cornerRadius, radius)
quadraticBezierTo(width - 1.2f*cornerRadius, 1.2f*cornerRadius, width - radius, cornerRadius)
quadraticBezierTo(width, cornerRadius, width, cornerRadius + radius)
lineTo(width, height - radius)
quadraticBezierTo(width, height, width - radius, height)
lineTo(radius, height)
quadraticBezierTo(0f, height, 0f, height - radius)
lineTo(0f, radius)
}
}
2. Apply the shape to the Card
:
Card(
modifier = Modifier.fillMaxSize(),
shape = shape
) {
content()
}
3. Add a circular Box
:
Box(
modifier = Modifier
.size(cornerRadiusDp - padding)
.align(Alignment.TopEnd)
.background(
color = CardDefaults.cardColors().containerColor,
shape = CircleShape
)
)
4. Put the Card
and circular Box
together in a parent Box
:
Box(modifier = modifier) {
Card(
modifier = Modifier.fillMaxSize(),
shape = shape
) {
content()
}
Box(
modifier = Modifier
.size(cornerRadiusDp - padding)
.align(Alignment.TopEnd)
.background(
color = CardDefaults.cardColors().containerColor,
shape = CircleShape
)
)
}
Here’s a full example:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.GenericShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CustomCard(modifier = Modifier
.width(300.dp)
.height(250.dp)
) {
val price = buildAnnotatedString {
withStyle(SpanStyle(color = Color.Green)) { append("$") }
append("0.00")
}
val labelStyle = remember {
TextStyle(fontSize = 18.sp, fontWeight = FontWeight.Light)
}
val priceStyle = remember {
TextStyle(fontSize = 18.sp, fontWeight = FontWeight.Bold)
}
Column(
modifier = Modifier.padding(16.dp)
) {
Text(text = "Today", style = labelStyle)
Text(text = price, style = priceStyle)
Spacer(modifier = Modifier.weight(1f))
Text(text = "Yesterday", style = labelStyle)
Text(text = price, style = priceStyle)
Spacer(modifier = Modifier.weight(1f))
Text(text = "Past Week", style = labelStyle)
Text(text = price, style = priceStyle)
}
}
}
}
}
}
@Composable
fun CustomCard(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
val radius = with(LocalDensity.current) { 10.dp.toPx() }
val cornerRadiusDp = 50.dp
val cornerRadius = with(LocalDensity.current) { cornerRadiusDp.toPx() }
val padding = 5.dp
// Create a custom shape
val shape = remember {
GenericShape { size, _ ->
val width = size.width
val height = size.height
moveTo(0f, radius)
quadraticBezierTo(0f, 0f, radius, 0f)
lineTo(width - cornerRadius - radius, 0f)
quadraticBezierTo(width - cornerRadius, 0f, width - cornerRadius, radius)
quadraticBezierTo(width - 1.2f*cornerRadius, 1.2f*cornerRadius, width - radius, cornerRadius)
quadraticBezierTo(width, cornerRadius, width, cornerRadius + radius)
lineTo(width, height - radius)
quadraticBezierTo(width, height, width - radius, height)
lineTo(radius, height)
quadraticBezierTo(0f, height, 0f, height - radius)
lineTo(0f, radius)
}
}
Box(modifier = modifier) {
Card(
modifier = Modifier.fillMaxSize(),
shape = shape
) {
content()
}
Box(
modifier = Modifier
.size(cornerRadiusDp - padding)
.align(Alignment.TopEnd)
.background(
color = CardDefaults.cardColors().containerColor,
shape = CircleShape
)
)
}
}
Demo:
For another example, refer to this answer.