androidkotlinandroid-jetpack-compose

How can i make Box or Card similar to my design


I would like to create a design similar to the picture below.

Image of my design

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.


Solution

  • 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:

    Demo

    For another example, refer to this answer.