Jetpack Compose Canvas

Jetpack Compose Canvas

Vikas Kumar
FalabellaTechnology

--

Jetpack Compose has recently graduated to become PROD-ready as it has just hit the 1.0.0 milestoneCanvas. While the android community has started adopting for building the apps and prototyping around the next generation android UI framework.

While the Jetpack Compose is full of exciting APIs to work with but this time I decided to try out the Canvas API of the Jetpack Compose. Native Canvas API was the least explored territory for me until now. Jetpack Compose Canvas has made it easier to create custom UI components that are not available and are now part of the main UI APIs instead of inheriting some View and overriding onDraw, it’s much easier to use and is directly available like other Compose UI elements.

Today we will try to learn the basics of the Jetpack Compose Canvas APIs. Using those we will create some cool icons as well like the below ones.

Icon pack developed using Compose Canvas API

Canvas is like the developer’s Photoshop. You can go wild with your imaginations and create things that are simply awesome. We will start with the basic shapes and then move slowly to understand the different functions available to create the custom UI elements or graphics.

Jetpack Compose Canvas

The Jetpack Compose canvas under the hood is Native android canvas API’s from the UI toolkit but more accessible and is a lot easier to use and create things. The Canvas is exposed as a composable function in the Jetpack Compose UI framework and you can place the Canvas in your layout the same way you would with any other Compose UI element.
It simplifies a lot of things for us and handles different Canvas drawing states for us. We all know working with those Paint objects was so difficult and can easily cause performance issues as well. These things are already handled by Jetpack Compose Canvas API.

From Compose Official Docs

  • Compose minimizes the state in its graphic elements, helping you avoid state’s programming pitfalls.
  • When you draw something, all the options are right where you’d expect them, in the composable function.
  • Compose’s graphics APIs take care of creating and freeing objects in an efficient way.

Within the, Canvas we can draw our elements with fine control over their positioning and styling. Below code creates an empty Canvas of size 100dp with padding of 16dp. It exposes a DrawScope which maintains its drawing environment states and provides useful information about the Size and many other things.

@Composable
fun icon() {
Canvas(
modifier = Modifier
.size(100.dp)
.padding(16.dp)
) {
//draw shapes here
}
}

Let’s understand our Canvas first 🎨

The mobile or desktop screen is composed of pixels and millions of pixels come together to form a single screen. If we have to see at the atomic level it will be something like below.

Higher(2) and Lower(1) pixels distribution on a sample screen of 242 X 242 with coordinates

We can see how the pixels are arranged according to coordinates starting from (0,0) to (242,242). The left to right direction forms the X-axis and top to bottom form the Y-axis. So every pixel has some coordinates and this forms the foundation for positioning the UI elements on the screen. Using this knowledge we will also position our shape or custom UI elements on the Canvas. Canvas gives us control over how and where we want to put our UI elements or shapes.

1) Instagram Icon (Rounded Rectangle + Circle + Gradient Colors)

We will see how we can use the Rounded Rectangle with the circle to create an Instagram icon with gradient color as the background of the shapes.

Instagram icon

As we know now that each element has some position on the screen. We will start also by creating an empty canvas of size (100x100) and position our shapes one by one in approximate positions to build our Instagram icon. You can create a canvas of any size within the screen boundaries. To keep the thing simple we have kept it of size 100dp.

Our Instagram icon needs three shapes as we can see in the above image

  • Rounded Rectangle starting with the offset of (0,0) by default.
  • Circle with the offset of (50,50) by default, circle is placed in the middle of the canvas.
  • Circle with an offset of (80,20)which means 80 points far from the X-axisand20 points far from theY-axis.

For Styling, we need the gradient as background color and one more thing to notice is that we need to make our first rounded rectangle and middle circle as stroked fill which means it won’t fill the entire space instead it will be line based with some width. So in theory we are clear, now we will go to our Android Studio and do some coding.

drawRoundRect() draws a rectangle with corners rounded, we applied Stroke as a style. Here you can have the option to fill the entire shape with some color or just apply some Gradient. I choose gradient given the look and feel of an Instagram icon. The same goes for the drawCircle() function you can either create a stroked one or a filled one. By default, its alignment is in the middle of the canvas space. In the end, we have one small circle filled with some custom positioning. When combined this forms our Instagram icon.

2) Facebook Icon(Rounded Rectangle + Text Custom Font + Color)

By drawing facebook icons we will learn how to use custom typefaces if we have to draw some text on the canvas.

Facebook icon

The Facebook icon is a simple one but the interesting part is to draw a custom font typeface on the canvas. As default, our Rounded rectangle will start from the (0,0) offset. We have given a solid blue(#1776d1) color to our shape. A corner radius 20 is applied to form a curve at all four edges as needed to match our UI.

Now coming to the drawing a text on canvas. So for now we don’t have any Compose-based Canvas API available to draw the text on canvas. In order to do so, we need to access the underlying native android canvas and draw text. With the help of LocalContext we can access the assets object and then Create a Paint object as we do today as part of drawing text on canvas. To access the inner native canvas we have a special attribute under the DrawScope (drawContext.canvas.nativeCanvas.drawTextwith which we can access the canvas object and call drawText() to draw “f” on a given position. That’s all we need to create a shinning icon for Facebook. It’s interesting with such a small piece of code we are able to create a Facebook icon instantly 😊.

3) Messenger Icon (Path + Oval + Gradient Color)

Now coming to a bit interesting one, in the previous two we learned about the inbuilt shapes and how to use them but now it’s time to create some custom shapes using Path API in canvas with an Oval shape forming a messenger icon. This is how our messenger icon looks like.

Facebook messenger icon

Our Messenger icon is somehow interesting and needs a special oval shape, the electric shape and the triangle at the left bottom are custom shapes. We will use Path for this. First, we need to point to a location from where we want to start or drawing a path. Then we will draw lines on the below-given coordinates to form an electric shape. Then similarly we will draw a triangle path at the bottom to form a chat bubble edge. When combined with all other shapes and brushing our icon with some shades of blue will form a messenger icon.

In our case, we just needed the lines and it forms the electric shape and triangle but a more complex shape can also be achieved using other shapes and curves available under the Path drawing scope. Don’t forget to close your paths in the end.

4) Google Photos (Arc + Color)

Till now we have talked about the circles filled and stroked but what if we just need to fill the half of the circle but not full. Like half potion of the circle or the one-fourth of a circle. This is where ARC shines this allows us to draw semi-circles from desired angles. To complete our Google photos we need exactly this to accomplish our Icon. As we can see below we need four different half circles aligned on different angles to form a google photos icon.

Google photos icon

One interesting thing to remember is that drawing arc needs starting angle and sweep angle. The start angle is always in the clockwise direction. Then we can define the sweep angle based on the sweep angle that parts of the circle will be drawn. In our case, we needed a half circle so we made a sweep angle of 180°. You can see in the left image we are starting at a different angle with a sweep of 180° in clock or anti-clock direction to achieve what we want. All 4 semi-circles are aligned to different offsets with arcs at different angles. Now if we see the code it will be much easier to understand what's going on here.

We can also define the Size for shapes as well and there is an option to useCenter which is basically asking if want to consider the center of the circle this will be helpful in case we want to achieve some wedge kind of a shape. But in our case, it's of less significance since we are drawing a semi-circle.

5) iOS Weather App Icon (Cubic + Circle + Rounded Rectangle + Gradient)

This one is going to be most exciting since this will help us in creating the most unique and complex shape till now in the Compose canvas. In the Weather icon, we have a sun and a cloud with some background. I guess you already guessed it right that we need some kind of curve shapes here for the cloud part but how do we create them. To clear our doubts let's follow below crash course 😀 on curves and how to create them.

Bezier curves

Bezier curves give us the flexibility of defining curves with precise control. This is the technique widely used in computer graphics to draw some complex graphics. While doing this experiment with Compose canvas I came to know more about Quadratic and Cubic bezier curves. The main difference between the two is the no. of control points between them. Quadratic has one control point and two other points are starting and ending points. Cubic bezier curves whereas have two control points and two for starting and ending points.

Quadratic & Cubic bezier Curve from left to right

I guess we understood a little bit about the curves now and will apply this newly gained knowledge into some practice. So according to the given weather icon, we need a cloud and a sun. Sun can be drawn using a Full circle with yellow color and for cloud, we will use the bezier curves as by now we know about curve shapes but wait a minute which bezier curve serves the purpose Quadratic or Cubic ?. I hope the below diagram shows which one fits here.

Weather Icon with Cubic Control Points

So from the cloud shape anatomy on the right side, it's quite evident that this can be done by using a Cubic curve only. And we can see how Cubic the curve has two control points and source and destination point to form a cloud shape. Here each control point can bend the shape by just changing the coordinates. It's quite enjoyable if you play with it to create different shapes.

Okay, enough theory let's jump to code to see how it's done in Compose canvas.

If you observe the code and match it with the above images it will be quite clear how we have used a drawRoundRect()with drawCircle() and drawPath() to create our cloud with some gradient background and altogether forms a weather icon.

Summary of the things we have used in the project.

So we have just covered some of the unique concepts in this blog post and there are more in the code. This Compose Canvas Icons project is attached below for code reference. Here are the concepts which we have used so far in the project and the blog post. So go and explore the project below to see how these concepts are used in Code to create icons.

  • drawRoundRect() — Draws a rounded rectangle with the provided size, offset and radii for the x and y axis respectively.
  • drawCircle() — Draws a circle at the provided center coordinate and radius. If no center point is provided the center of the bounds is used.
  • drawText() — Draw the specified range of text, specified by start/end, with its origin at (x,y), in the specified Paint.
  • drawOval() — Draws an oval with the given offset and size. If no offset from the top left is provided, it is drawn starting from the origin of the current translation.
  • drawPath() — Draws the given Path with the given Color. Whether this shape is filled or stroked (or both) is controlled by DrawStyle.
  • clipPath() — Reduces the clip region to the intersection of the current clip and the given path. This method provides a callback to issue drawing commands within the region defined by the clipped path.
  • drawRect() — Draws a rectangle with the given offset and size. If no offset from the top left is provided, it is drawn starting from the origin of the current translation.
  • drawArc() — Draw an arc scaled to fit inside the given rectangle. It starts from startAngle degrees around the oval up to startAngle + sweepAngle degrees around the oval, with zero degrees being the point on the right hand side of the oval that crosses the horizontal line that intersects the center of the rectangle and with positive angles going clockwise around the oval.
  • rotate() — Add a rotation (in degrees clockwise) to the current transform at the given pivot point. The pivot coordinate remains unchanged by the rotation transformation.
  • drawIntoCanvas() — Provides access to draw directly with the underlying Canvas. This is helpful for situations to re-use alternative drawing logic in combination with DrawScope.
  • cubicTo() — Adds a cubic bezier segment that curves from the current point to the given point (x3, y3), using the control points (x1, y1) and (x2, y2).
  • quadraticBezierTo() — Adds a quadratic bezier segment that curves from the current point to the given point (x2, y2), using the control point (x1, y1).
  • lineTo() — Adds a straight line segment from the current point to the given point.
  • drawLine() — Draws a line between the given points using the given paint.

Helpful resources and links.

Project for Jetpack Compose Canvas API.

Please feel free to fork or download the project and start exploring some of the concepts of the drawing graphics in Jetpack Compose Canvas. Your feedback and suggestions are always welcome 🙏.

--

--