androidkotlinandroid-jetpack-composeandroid-jetpack

How to create a calendar date card with a wave effect in Jetpack Compose


I would like to create a calendar date card in Jetpack Compose that closely matches this design, maintaining the same wave patterns and path shapes. My goal is to ensure that the visual elements, such as the curves and overall layout.

enter image description here

Thank you so much for your help


Solution

  • You can achieve it by using below code block

    Output would be look like Output Image

    package com.example.demo
    
    import androidx.compose.foundation.Canvas
    import androidx.compose.foundation.background
    import androidx.compose.foundation.layout.Arrangement
    import androidx.compose.foundation.layout.Box
    import androidx.compose.foundation.layout.Column
    import androidx.compose.foundation.layout.fillMaxWidth
    import androidx.compose.foundation.layout.height
    import androidx.compose.foundation.layout.padding
    import androidx.compose.foundation.layout.size
    import androidx.compose.foundation.shape.RoundedCornerShape
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Alignment
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.draw.clip
    import androidx.compose.ui.geometry.Size
    import androidx.compose.ui.graphics.Color
    import androidx.compose.ui.graphics.Outline
    import androidx.compose.ui.graphics.Path
    import androidx.compose.ui.graphics.Shape
    import androidx.compose.ui.graphics.drawscope.Stroke
    import androidx.compose.ui.text.font.FontWeight
    import androidx.compose.ui.tooling.preview.Preview
    import androidx.compose.ui.unit.Density
    import androidx.compose.ui.unit.LayoutDirection
    import androidx.compose.ui.unit.dp
    import androidx.compose.ui.unit.sp
    
    @Preview
    @Composable
    fun WaveCardWithOutline() {
        Box(
            modifier = Modifier
                .size(100.dp)
        ) {
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(94.dp)
                    .clip(RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp))
                    .clip(waveShape())
                    .background(Color.Red),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                Text(
                    text = "20",
                    fontSize = 32.sp,
                    fontWeight = FontWeight.Bold,
                    color = Color.White
                )
                Text(
                    text = "Dec",
                    fontSize = 24.sp,
                    fontWeight = FontWeight.Medium,
                    color = Color.White
                )
            }
    
            Canvas(
                modifier = Modifier
                    .matchParentSize()
            ) {
                val path = createBottomOutlinePath(size)
                drawPath(path, color = Color.Red, style = Stroke(width = 2.dp.toPx()))
            }
        }
    }
    
    fun waveShape(): Shape = object : Shape {
        override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline {
            val path = createWavePath(size)
            return Outline.Generic(path)
        }
    }
    
    fun createWavePath(size: Size): Path {
        return Path().apply {
            moveTo(0f, 0f)
            lineTo(0f, size.height - 10f)
    
            val waveHeight = 10f
            val waveWidth = size.width / 5
    
            for (i in 0 until 5) {
                val x1 = (i * waveWidth) + (waveWidth / 2)
                val y1 = if (i % 2 == 0) size.height - (waveHeight * 2) else size.height
                val x2 = (i + 1) * waveWidth
                val y2 = size.height - 10f
    
                quadraticBezierTo(x1, y1, x2, y2)
            }
    
            lineTo(size.width, 0f)
            close()
        }
    }
    
    fun createBottomOutlinePath(size: Size): Path {
        return Path().apply {
            val waveHeight = 10f
            val waveWidth = size.width / 5
    
            moveTo(0f, size.height - 10f)
    
            for (i in 0 until 5) {
                val x1 = (i * waveWidth) + (waveWidth / 2)
                val y1 = if (i % 2 == 0) size.height - (waveHeight * 2) else size.height
                val x2 = (i + 1) * waveWidth
                val y2 = size.height - 10f
    
                quadraticBezierTo(x1, y1, x2, y2)
            }
        }
    }