Graphics in Jetpack Compose

Jayesh Seth
6 min readFeb 25, 2023

--

Photo by Birmingham Museums Trust on Unsplash

Graphics in Android has always been a tricky and abandoned area due their complexity and the setup required to perform even basic drawing. Google saw that and made it easy and simple with the introduction of Jetpack Compose and its easier API using Canvas.

It is like Photoshop but for developers, and literally does what it's called — it provides a blank canvas for developers to draw on. Let’s say your Android app requires some sort of custom graphics, it could be as simple as putting a circle in a box, or fine-grained control of graphic elements. Compose’s declarative UI approach makes it simpler in an efficient way.

Canvas

Canvas acts exactly how you would expect a composable to function because under the hood Canvas is basically a Spacer with a drawBehind modifier. Canvas exposes a drawScope, but what even is the draw scope?

DrawScope

Draw Scope is a scoped drawing environment that maintains its own state; it also provides pre-built functions like:

  • drawRect
  • drawRoundRect
  • drawCircle
  • drawOval
  • drawLine
  • drawArc
  • drawPath
  • drawPoints

Canvas Graph

Canvas can be customized to your heart’s will with the help of X & Y axis . Draw scope provides its default coordinates [0,0] on the top left of the canvas. To understand how the coordinate system in compose works, let's take this graph as an example.

the origin point [0,0] of coordinate system is on the top left. X - increases as it moves to the right and Y - increases as it moves downwards.

DrawRect

Implementation of drawRect is simple, it has many parameters that you can pass to customize you drawRect object

Canvas(
modifier = Modifier
.height(200.dp)
.width(300.dp)
) {
drawRect(
color = color,
size = size,
alpha = Float,
style = DrawStyle,
blendMode = BlendMode,
colorFilter = ColorFilter,
topLeft = Offset
)
}

In the following code, we define a height and width to shape it like a rectangle. You can modify these parameters to customize your rectangle, check GitHub repo below to look at the implementation here.

The advantage of using Canvas in Jetpack Compose is that many of its concepts are similar, so we will concentrate solely on the parameters that are specific to them.

Draw Circle

To learn how center offset works in drawCircle. We can give our canvas a background. Center in drawCircle is the X & Y coordinate offset of the center point of our drawCircle object. X moves to the left/right and Y and moves upward/downward.

Code sample:

        Canvas(
modifier = Modifier
.size(20.dp)
.background(color = Color.Magenta)
) {
drawCircle(
color = color,
center = Offset(
x = size.height,
y = size.width
),
)
}

Thus center point moves to the bottom right of the canvas.

drawCircle and drawOval both follow basic principles except drawOval takes dimensions of the rectangle to draw.

Draw Line

drawLine takes two offsets to draw the line on screen start [X&Y] and end [X&Y] then draws a straight line from the straight point to the endpoint,

we can also add stroke width to make the line more visible. PathEffect opens up a whole bunch of customization capabilities, and lets you add multiple kinds of effects, like dashEffect, make it a dotted line, and so on.

XO board drawn using drawLine

Draw Arc

Arc takes the dimensions of a circle canvas. It starts from startAngle and sweeps to sweepAngle. Sweep Angle draws relative to start angle

With positive sweep values it travels clockwise and anti-clockwise for negative values.

The useCenter parameter indicates if the arc should be close to the center of the arc or relative to topLeft

Draw Path & Draw Point

drawPath and drawPoint are pretty similar to each other. In drawPath to draw a path first we need to create a reference to Path() method to create coordinates or give directions for the canvas to draw,

For example:

 val path = Path()
path.moveTo(size.width, 0f)
path.lineTo(size.width, size.height)
path.lineTo(0f, size.height)

drawPath(
color = color,
path = path,
)

Output:

But how does it get its shape and look like a right-angled triangle? Well, to break it down let’s change its draw style to stroke.

Now we can understand better how it works — we first move our coordinates to the width of the screen and then draw lines. But it still doesn’t answer the right-angled shape, you say?

Since the default draw style is filled Path(), the sub-paths within it are implicitly closed and thus we get a right-angled triangle.

for drawPoint on the other hand you pass list of offsets you want canvas to draw on.

val points = listOf(
Offset(size.width, 0f),
Offset(size.width, size.height),
Offset(0f, size.height),
Offset(size.width, 0f),
)

Output:

draw points have multiple pointModes to customize paths.

PointMode.Points
PointMode.Polygon
PointMode.Lines
PointMode.Lines

Advantages Of Canvas

  • Compose minimizes state in its graphics element avoiding state pitfalls.
  • Everything you draw is a composable function.
  • Compose behind the scene takes care of creating and freezing objects efficiently.

Bonus Links

Github Source Code:

Learn more about draw scope here:

Learn more about Compose:

Kotlin Coroutines:

Wanna try something cross platform? learn Flutter:

Power of chatgpt in flutter:

Spice things up with an introduction to AOSP:

--

--