Canvas in Jetpack Compose
Canvas in Jetpack Compose is used for custom drawing and graphics rendering that goes beyond the standard UI components.
It allows developers to create unique shapes, animations, and effects that aren’t possible with built-in composables.
Canvas in Jetpack Compose provides limitless possibilities for custom UI designs, animations, and interactive effects. By mastering Canvas, you can create unique and dynamic experiences in your Android apps.
Jetpack Compose provides a powerful Canvas API that allows developers to create custom drawings, animations, and effects.
With Canvas, you can draw shapes, paths, text, and images, giving you full control over UI elements.
Why Use Canvas in Jetpack Compose?
- ✅ Full Control – Draw anything pixel-perfect.
- ✅ Lightweight & Efficient – Avoids unnecessary recompositions.
- ✅ Custom Animations – Beyond standard UI animations.
- ✅ Interactive UI – Gestures, touch-based interactions.
- ✅ Optimized Performance – Hardware-accelerated rendering.
How to Position an object on Screen in Jetpack Compose Canvas
The x and y coordinates determine where the object is drawn.
Position | Offset Formula |
---|---|
Center | Offset(size.width / 2, size.height / 2) |
Top-Left | Offset(radius, radius) |
Top-Right | Offset(size.width - radius, radius) |
Bottom-Left | Offset(radius, size.height - radius) |
Bottom-Right | Offset(size.width - radius, size.height - radius) |
Custom | Offset(x, y) |
@Composable
fun CanvasDemoScreen() {
val imageBitmap = ImageBitmap.imageResource(R.drawable.image)
Canvas(modifier = Modifier.fillMaxSize()) {
drawImage(
image = imageBitmap,
topLeft = Offset(70f, 150f)
)
}
}
1. Basic Drawing Functions
These functions allow you to draw simple geometric shapes.
drawRect() - Draws a circle
OUTPUT:package com.example.jetpackskill
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
@Composable
fun CanvasDemoScreen() {
Canvas(modifier = Modifier.fillMaxSize()) {
drawCircle(
color = Color.Red,
radius = 100f,
center = Offset(size.width / 2, size.height / 2)
)
}
}
- size: Represents the dimensions of the canvas.
- Offset(x, y): Defines a position on the canvas.
- drawCircle(), drawRect(), drawLine(), etc.: Functions to draw basic shapes.
2. Drawing Advanced Shapes
How to use Path to create complex shapes.
package com.example.jetpackskill
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
@Composable
fun CanvasDemoScreen() {
Canvas(modifier = Modifier.fillMaxSize()) {
val path = Path().apply {
moveTo(100f, 100f)
lineTo(300f, 100f)
lineTo(200f, 300f)
close()
}
drawPath(path, color = Color.Blue)
}
}
OUTPUT:
- Path.moveTo(x, y): Moves the starting point of the path.
- lineTo(x, y): Draws a line from the current point to (x, y).
- close(): Closes the shape, connecting the last point to the first.
3. Applying Transformations (Translate, Rotate, Scale)
We use rotate(), translate(), and scale() for transformations.
OUTPUT:package com.example.jetpackskill
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.rotate
@Composable
fun CanvasDemoScreen() {
Canvas(modifier = Modifier.fillMaxSize()) {
rotate(45f, pivot = Offset(size.width / 4, size.height / 2)) {
drawRect(Color.Green, size = Size(200f, 200f))
}
}
}
- rotate(angle, pivot): Rotates the content.
- translate(x, y): Moves the drawing position.
- scale(factorX, factorY): Scales the drawing.
4. Animating Canvas Drawings
For animations, use rememberInfiniteTransition() with animated values.
package com.example.jetpackskill
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@Composable
fun CanvasDemoScreen() {
val infiniteTransition = rememberInfiniteTransition()
val animatedRadius by infiniteTransition.animateFloat(
initialValue = 50f,
targetValue = 150f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 1000,
easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
)
Canvas(modifier = Modifier.fillMaxSize()) {
drawCircle(Color.Magenta, radius = animatedRadius, center = center)
}
}
- rememberInfiniteTransition(): Keeps the animation running.
- animateFloat(): Animates a float property.
- infiniteRepeatable(): Loops the animation infinitely.
5. Handling Touch Input in Canvas
We can use Modifier.pointerInput() to detect user gestures.
package com.example.jetpackskill
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
@Composable
fun CanvasDemoScreen() {
var position by remember { mutableStateOf(Offset(200f, 200f)) }
Canvas(modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures { tapOffset -> position = tapOffset }
}
) {
drawCircle(Color.Cyan, radius = 50f, center = position)
}
}
- detectTapGestures { tapOffset }: Detects tap events.
- utableStateOf(Offset()): Stores the tap position.
Output:
Lets draw text in Canvas using gradient brush
To draw text what we are going to use?
- Canvas Composable: The drawText function is part of DrawScope, which is available inside the Canvas composable.
- textMeasurer.measure(...): This measures the text before drawing it.
- drawText(...): This is called inside the Canvas drawing scope.
- Bigger and bolder text: (40.sp, FontWeight.Bold).
- White text with a black shadow: for better visibility.
- Gradient color effect using Brush.linearGradient.
- Blurred shadow: for a cool glowing effect.
package com.example.jetpackskill
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.sp
@Composable
fun CanvasScreenDemo(modifier: Modifier = Modifier) {
val textMeasurer = rememberTextMeasurer()
Canvas(modifier = modifier.fillMaxSize()) {
val textLayoutResult = textMeasurer.measure(
text = "Hello, Canvas!",
style = TextStyle(
fontSize = 40.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
shadow = Shadow(
color = Color.Black, // Shadow color
offset = Offset(4f, 4f), // Shadow offset
blurRadius = 8f // Shadow blur
)
)
)
// Create a gradient effect using a linear gradient shader
val gradientBrush = Brush.linearGradient(
colors = listOf(Color.Magenta, Color.Cyan, Color.Blue),
start = Offset.Zero,
end = Offset(300f, 0f)
)
drawText(
textLayoutResult,
topLeft = Offset(100f, 200f),
brush = gradientBrush // Apply the gradient color
)
}
}