Jetpack Compose Canvas: Custom Drawing, Charts & Animations
Jetpack Compose's Canvas API empowers you to create custom graphics, data visualizations, and fluid animations without fighting the Android graphics pipeline. Let's explore the powerful drawing primitives and real-world patterns.
Basic Drawing Primitives
drawCircle, drawRect, drawLine, drawArc
Canvas(modifier = Modifier.fillMaxSize()) {
// Draw a circle at center
drawCircle(
color = Color.Blue,
radius = 50.dp.toPx(),
center = center
)
// Draw a rectangle
drawRect(
color = Color.Red,
topLeft = Offset(100f, 100f),
size = Size(200f, 150f)
)
// Draw a line
drawLine(
color = Color.Green,
start = Offset(0f, 0f),
end = Offset(size.width, size.height),
strokeWidth = 4f
)
// Draw an arc
drawArc(
color = Color.Yellow,
startAngle = 0f,
sweepAngle = 270f,
useCenter = true,
topLeft = Offset(50f, 50f),
size = Size(100f, 100f)
)
}
Advanced: Path & TextMeasurer
Path API enables complex shapes by combining lines, curves, and arcs:
val path = Path().apply {
moveTo(size.width / 2, 0f)
lineTo(size.width, size.height / 2)
lineTo(size.width / 2, size.height)
lineTo(0f, size.height / 2)
close()
}
drawPath(path, color = Color.Magenta, style = Stroke(width = 2f))
TextMeasurer measures text dimensions before drawing:
val textMeasurer = rememberTextMeasurer()
Canvas(modifier = Modifier.fillMaxSize()) {
val textLayoutResult = textMeasurer.measure(
text = AnnotatedString("Hello Compose"),
style = TextStyle(fontSize = 20.sp)
)
drawText(textLayoutResult, topLeft = Offset(50f, 50f))
}
Data Visualization: Bar Chart
@Composable
fun BarChart(data: List<Float>) {
Canvas(modifier = Modifier.fillMaxWidth().height(300.dp)) {
val barWidth = size.width / data.size
val maxValue = data.maxOrNull() ?: 1f
data.forEachIndexed { index, value ->
val barHeight = (value / maxValue) * size.height
drawRect(
color = Color.Blue,
topLeft = Offset(index * barWidth, size.height - barHeight),
size = Size(barWidth * 0.8f, barHeight)
)
}
}
}
Data Visualization: Line Chart
@Composable
fun LineChart(points: List<Float>) {
Canvas(modifier = Modifier.fillMaxWidth().height(300.dp)) {
val pointCount = points.size
val xStep = size.width / (pointCount - 1)
val maxValue = points.maxOrNull() ?: 1f
val path = Path()
points.forEachIndexed { index, value ->
val x = index * xStep
val y = size.height - (value / maxValue) * size.height
if (index == 0) path.moveTo(x, y) else path.lineTo(x, y)
}
drawPath(path, Color.Blue, style = Stroke(width = 3f))
}
}
Data Visualization: Pie Chart
@Composable
fun PieChart(data: Map<String, Float>) {
Canvas(modifier = Modifier.size(300.dp)) {
val total = data.values.sum()
val center = Offset(size.width / 2, size.height / 2)
val radius = size.width / 3
var startAngle = 0f
data.forEach { (_, value) ->
val sweepAngle = (value / total) * 360f
drawArc(
color = Color(kotlin.random.Random.nextInt(0xFF000000.toInt(), 0xFFFFFFFF.toInt())),
startAngle = startAngle,
sweepAngle = sweepAngle,
useCenter = true,
size = Size(radius * 2, radius * 2),
topLeft = center - Offset(radius, radius)
)
startAngle += sweepAngle
}
}
}
Animated Progress Ring
Combine Canvas with Compose's animation system:
@Composable
fun AnimatedProgressRing(progress: Float) {
val animatedProgress = animateFloatAsState(progress)
Canvas(modifier = Modifier.size(200.dp)) {
drawArc(
color = Color.LightGray,
startAngle = 0f,
sweepAngle = 360f,
useCenter = false,
style = Stroke(width = 8f)
)
drawArc(
color = Color.Blue,
startAngle = -90f,
sweepAngle = animatedProgress.value * 360f,
useCenter = false,
style = Stroke(width = 8f)
)
}
}
Performance Tips
-
Measure expensive operations: Use
rememberTextMeasurer()to avoid repeated measurements -
Batch drawing calls: Group related
drawX()calls together -
Use
rememberfor paths: Recalculate paths only when data changes -
Optimize for rotation: For rotated elements, use
translate()androtate()to transform the canvas
The Canvas API is your gateway to rich, interactive visualizations and custom UI experiences in Compose.
8 Android App Templates → https://myougatheax.gumroad.com
Top comments (0)