I'm trying to create a custom card in Jetpack Compose that has a specific shape. I want the card to resemble a particular shape (as shown in the image below) with rounded corners and a custom contour.
I've started with the code below, but I'm unsure how to adjust the curves and the shape to match the image exactly. Here’s the target card shape image:
How can I make a background similar to the picture?
Here is my code, I have tried but couldn't achieve similar shape.
@Composable
fun CustomCard(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
val radius = with(LocalDensity.current) { 19.dp.toPx() }
val cornerRadiusDp = 80.dp
val cornerRadius = with(LocalDensity.current) { cornerRadiusDp.toPx() }
val padding = 10.dp
// Create a custom shape
val shape = remember {
GenericShape { size, _ ->
val width = size.width
val height = size.height
moveTo(0f, radius)
quadraticTo(0f, 0f, radius, 0f)
lineTo(width - cornerRadius - radius, 0f)
quadraticTo(width - cornerRadius, 0f, width - cornerRadius, radius)
quadraticTo(width - 1.1f*cornerRadius, 1.1f*cornerRadius, width - radius, cornerRadius)
quadraticTo(width, cornerRadius, width, cornerRadius + radius)
lineTo(width, height - radius)
quadraticTo(width, height, width - radius, height)
lineTo(radius, height)
quadraticTo(0f, height, 0f, height - radius)
lineTo(0f, radius)
}
}
Box(modifier = modifier) {
Card(
modifier = Modifier.fillMaxSize(),
shape = shape,
colors = CardDefaults.cardColors(
containerColor = Color(0xFFBFE8FB)
)
) {
content()
}
Box(
modifier = Modifier
.size(cornerRadiusDp - padding)
.align(Alignment.TopEnd)
.background(
color = Color.Blue,
shape = CircleShape
)
){
Text(
text = "1",
color = Color.White,
modifier = Modifier.align(Alignment.Center)
)
}
}
}
@Preview
@Composable
fun ContextCardPreview() {
Column(modifier = Modifier.background(Color.Red)){
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CustomCard(modifier = Modifier
.width(200.dp)
.height(180.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(10.dp)
) {
Text(text = "Text", fontSize = 18.sp)
//Text(text = price, style = priceStyle)
Spacer(modifier = Modifier.weight(0.8f))
Text(text = "Text", style = labelStyle)
Spacer(modifier = Modifier.weight(0.5f))
Text(text = price, style = priceStyle)
Spacer(modifier = Modifier.weight(0.04f))
}
}
}
}
}
This is an improved version of this answer, which gives you even more control over the outline of the shape.
/**
* cornerRadius : control the corner radius
* gapCornerRadius : radius of the gap corner
* gapSize : size of the gap
* padding : padding between gap and button circle
* concavityFactor : control the concavity of the gap
*/
@Composable
fun CustomCard(
modifier: Modifier = Modifier,
cornerRadius : Dp = 20.dp,
gapCornerRadius : Dp = 30.dp,
gapSize : Dp = 80.dp,
padding: Dp = 10.dp,
concavityFactor: Float = 1.05f,
content: @Composable () -> Unit,
) {
val cornerRadiusPx = with(LocalDensity.current) { cornerRadius.toPx() }
val gapCornerRadiusPx = with(LocalDensity.current) { gapCornerRadius.toPx() }
val gapSizePx = with(LocalDensity.current) { gapSize.toPx() }
// Create a custom shape
val shape = remember {
GenericShape { size, _ ->
val width = size.width
val height = size.height
moveTo(0f, cornerRadiusPx)
quadraticTo(0f, 0f, cornerRadiusPx, 0f)
lineTo(width - gapSizePx - gapCornerRadiusPx, 0f)
quadraticTo(width - gapSizePx, 0f, width - gapSizePx, gapCornerRadiusPx)
quadraticTo(width - concavityFactor*gapSizePx, concavityFactor*gapSizePx, width - gapCornerRadiusPx, gapSizePx)
quadraticTo(width, gapSizePx, width, gapSizePx + gapCornerRadiusPx)
lineTo(width, height - cornerRadiusPx)
quadraticTo(width, height, width - cornerRadiusPx, height)
lineTo(cornerRadiusPx, height)
quadraticTo(0f, height, 0f, height - cornerRadiusPx)
lineTo(0f, cornerRadiusPx)
}
}
Box(modifier = modifier) {
Card(
modifier = Modifier.fillMaxSize(),
shape = shape,
colors = CardDefaults.cardColors(
containerColor = Color(0xFFBFE8FB)
)
) {
content()
}
Box(
modifier = Modifier
.size(gapSize - padding)
.align(Alignment.TopEnd)
.background(
color = Color.Blue,
shape = CircleShape
)
){
Text(
text = "1",
color = Color.White,
modifier = Modifier.align(Alignment.Center)
)
}
}
}
You can play with the CustomCard
parameters to see the effect of each argument. This way you can use whatever values you like.
Here is an image to see each parameter visually: