Simple Graphics
Graphics Overview
Building custom components with Jetpack Compose is significantly easier than it used to be with the old View approach!
In this module, we'll take a quick look at the old way and compare it to Compose, then dive in for some progressively more complex custom components, drawing everything ourselves to create exactly the UI we would like.
The canvas
Drawing in compose happens on a Canvas. The Canvas()
composable gives you functions to
- draw lines, rectangles, ovals, etc
- apply rotation, scaling, translation, clipping and other transforms
Ever since we used Cathode Ray Tube (CRT) displays, computers have typically used the upper-left corner of the screen as (0,0). This is because the CRT beam scans from left-to-right, top-to-bottom, starting at the upper left corner.
If you haven't worked with computer graphics before, this can be a bit uncomfortable, as you're probably used to Math's definition of (0,0) being at the lower left of the first quadrant (where x and y values are positive).
Compose's Canvas keeps the computer tradition. Canvas()
is passed a lambda where you perform
your drawing functions:
Canvas(modifier = ...) {
// drawing operations go here
}
As with all composable functions, your code will be executed wherever any values in the Compose snapshot change. Your drawing operations can be called very frequently, especially if you're animating something. Don't create new objects inside of the lambda unless you can remember and reuse them.
Note
In our code, we'll see what looks like a lot of object creation. For example,
Canvas(...) {
drawRect(
color = fillColor,
topLeft = Offset(0f,offsetY),
size = Size(size.width, fillHeight)
)
}
Offset(...)
and Size(...)
are value classes in Kotlin. Value classes act like normal
classes but only contain a single, primitive-value property defined in their primary
constructor. Instead of creating an object to wrap that property, that value is used directly.
Creating a custom component
To define a custom component in Compose, you just write a composable function that calls Canvas. You pass parameters in the same way we've be doing so far:
@Composable
fun CustomView(
color: Color,
value: Float,
modifier: Modifier = Modifier,
) {
Canvas(modifier = modifier) {
// "this" is DrawScope, giving access to drawing and translation functions
drawLine(...)
}
}
You can then use your custom component the same way you use any other composable:
CustomView(
color = Color.Blue,
value = 42f,
modifier = Modifier.fillMaxWidth(),
)