Graphics in Jetpack Compose

Graphics in Jetpack Compose
Photo by Birmingham Museums Trust / Unsplash

Graphics in Android has always has been a tricky and abandoned area due its 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 it's easier API using Canvas.

It is like Photoshop but for developers, and literally does what its 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?

Draw Scope

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 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.

Draw Rect

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.

drawRect

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 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.

screenshot from sample app

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,
            )
sample code to draw path in canvas
Output:
output for the code above

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.

style 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),
            )
sample for list of offsets
Output:
output of the above code

draw points have multiple pointModes to customize paths.

PointMode.Points
PointMode.Polygon
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:

GitHub - MadFlasheroo7/Compose-Canvas: Implementation of Canvas using jetpack compose in android
Implementation of Canvas using jetpack compose in android - GitHub - MadFlasheroo7/Compose-Canvas: Implementation of Canvas using jetpack compose in android

Learn more about draw scope here:

DrawScope | Android Developers

Learn more about Compose:

Getting Started with Jetpack Compose
Jetpack Compose is a modern UI toolkit by Google which replaces traditional XML layouts with a way to do it using Kotlin, which simplifies and accelerates UI development with less and maintainable code, allowing us to make powerful and beautiful apps in lesser time.

Wanna try something cross platform? learn Flutter:

Going from Native Android to Flutter
Flutter is an open-source UI toolkit created by Google to build and ship beautiful yet powerful applications across platforms with a single codebase. It supports platforms like iOS, macOS, Android, Web, Linux, Windows, and Fuchsia. I have been doing native Android development since 2019, starting w…

Spice things up with an introduction to AOSP:

Introduction to AOSP
The Android Open Source Project is an open source development project managed by Google, and anyone is welcome to evaluate and add code and fixes to the source code. Android System Architecture Let’s first talk about the layers of an Android device’s architecture before delving into the build sys…