How to Draw & Animate a Beating Heart in Jetpack Compose
Heart often refers to the stylized representation of the human heart, usually depicted as a symmetrical shape with a pointed bottom and rounded top, commonly associated with love, emotions, or symbols of affection. It’s frequently used in various forms of art, such as:
- Symbolism: Representing love, care, passion, and affection.
- Illustration: Often used in comics, greeting cards, tattoos, and other personal art expressions.
- Artistic Style: Can be drawn realistically to resemble an anatomical heart or in a simplified, cartoonish form, or even as an abstract design.
In many cultures and media, the heart symbol is universally understood as a representation of emotional or romantic feelings. Would you like tips on how to draw a heart, or any specific style of heart drawing?
How to build Sliding Puzzle 15 Game in Jetpack Compose?
Learning to draw a heart using Jetpack Compose—especially with cubic Bézier curves—is surprisingly useful in app development for several reasons:
✅ 1. Sharpens Your Canvas & Path Skills
- Drawing a heart involves using Canvas, Path, and curve logic.
- These are core for custom UI, animations, charts, games, and dynamic effects.
✅ 2. Real-World UI Use Cases
- Heart icons are everywhere: likes, favorites, reaction buttons.
- Creating custom hearts lets you animate, morph, or stylize them (e.g., glowing heart when liked).
✅ 3. Foundation for Drawing Anything Custom
- If you understand how to draw a heart with Bézier curves, you can draw stars, waves, logos, or even characters.
- It builds your skills for canvas-based apps, like drawing tools, diagram editors, or design apps.
✅ 4. Helps You in Interviews & Challenges
- Many interviews for Android UI roles ask for things like: “Draw a custom shape using Compose without any image.”
- Knowing Bézier curves and Canvas will help you stand out.
To draw a heart shape in Jetpack Compose, you can use the Canvas composable, which allows for custom drawing using a variety of shapes, including a heart.
Here's an example of how to draw a heart shape using Canvas in Jetpack Compose:
@Composable
fun RedHeartShape() {
Canvas(modifier = Modifier
.fillMaxSize()
.systemBarsPadding()) {
val canvasWidth = size.width
val canvasHeight = size.height
// Use center-based coordinates
val path = Path().apply {
moveTo(0f, 100f)
cubicTo(100f, -100f, 200f, 100f, 0f, 300f)
cubicTo(-200f, 100f, -100f, -100f, 0f, 100f)
}
withTransform({
translate(left = canvasWidth / 2, top = canvasHeight / 2 - 150f) // center heart
}) {
drawPath(path, Color.Red, style = Fill)
drawPath(path, Color.Black, style = Stroke(width = 8f, cap = StrokeCap.Round))
}
}
}
- Canvas: The Canvas composable is used to draw custom shapes and paths.
- Path: A Path object is used to define the shape of the heart using cubic Bézier curves.
- moveTo: Moves the pen to a specific coordinate.
- cubicTo: Draws a cubic Bézier curve between points.
- drawPath: This draws the heart path with a red color and a stroke of 5 pixels width.
Result:
How to make Red Heart Interactive
To make
@Composable
fun BeatingHeart() {
// Infinite scaling animation for heartbeat
val scale = rememberInfiniteTransition().animateFloat(
initialValue = 1f,
targetValue = 1.2f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 500,
easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
)
Box(
modifier = Modifier
.systemBarsPadding()
.fillMaxSize()
.graphicsLayer(
scaleX = scale.value,
scaleY = scale.value
)
) {
Canvas(modifier = Modifier
.fillMaxSize()
.systemBarsPadding()) {
val canvasWidth = size.width
val canvasHeight = size.height
// Use center-based coordinates
val path = Path().apply {
moveTo(0f, 100f)
cubicTo(100f, -100f, 200f, 100f, 0f, 300f)
cubicTo(-200f, 100f, -100f, -100f, 0f, 100f)
}
withTransform({
translate(left = canvasWidth / 2, top = canvasHeight / 2 - 150f) // center heart
}) {
drawPath(path, Color.Red, style = Fill)
drawPath(path, Color.Black, style = Stroke(width = 8f, cap = StrokeCap.Round))
}
}
}
}
How to build Animated heart drawing
Draw an animated heart shape using Jetpack Compose's Canvas, Path, and Animations.
- Composable function—used in Jetpack Compose to create UI. This one draws an animated heart.
- LaunchedEffect(Unit) runs once when the Composable is first launched.
- animateTo(1f) animates the value from 0 to 1 over 5 seconds (5000 ms).
- LinearEasing makes the animation smooth and consistent in speed.
- Canvas gives us a drawing space.
- Path is like a pen drawing lines and curves.
- moveTo moves to the bottom center of the heart.
- Two cubicTo() functions draw the right and left curves of the heart using Bezier curves.
- We use PathMeasure to find out the length of the heart path.
- Then getSegment() gives us only the portion of the path based on animation progress.
- So if pathProgress = 0.5, we draw half of the heart.
@Composable
fun AnimatedHeartDrawing() {
val pathProgress = remember { Animatable(0f) }
Box(
Modifier.fillMaxSize().systemBarsPadding(),
contentAlignment = Alignment.TopStart
) {
LaunchedEffect(Unit) {
pathProgress.animateTo(
targetValue = 1f,
animationSpec = tween(durationMillis = 5000,
easing = LinearEasing)
)
}
Canvas(
modifier = Modifier
.fillMaxWidth()
.height(500.dp)
.padding(32.dp)
) {
val path = Path().apply {
val width = size.width
val height = size.height
moveTo(width / 2, height / 5 * 3)
cubicTo(
width / 2 + width / 4, height / 5,
width / 2 + width / 2.5f, height / 5 * 2.5f,
width / 2, height
)
cubicTo(
width / 2 - width / 2.5f, height / 5 * 2.5f,
width / 2 - width / 4, height / 5,
width / 2, height / 5 * 3
)
}
val androidPath = path.asAndroidPath()
val pathMeasure = android.graphics.PathMeasure(androidPath, false)
val dst = android.graphics.Path()
// Animate in reverse: from end (length) down to 0
val start = pathMeasure.length * (1 - pathProgress.value)
val end = pathMeasure.length
pathMeasure.getSegment(start, end, dst, true)
drawPath(
path = Path().apply { addPath(dst.asComposePath()) },
color = Color.Red,
style = Stroke(width = 12f, cap = StrokeCap.Round)
)
}
}
}