Drawing Custom Shapes in Android
Learn how to draw custom shapes and paths in Android by creating a neat curved profile card with gradient colors. By Ahmed Tarek.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Drawing Custom Shapes in Android
30 mins
- Getting Started
- Exploring the Project
- Coding Your Shapes
- Know Your Canvas
- Defining How to Move Your Pencil
- Calculating Coordinates
- Using CustomPainter
- Implementing the Painter Interface
- Rendering With CustomPainter
- Drawing Your First Shape
- Drawing and Painting a Rectangle
- Using a Path to Draw the Profile Card
- Drawing the Profile Card
- Adding Negative Space Around the Avatar
- Creating the Rectangle Around the Avatar
- Adding a Margin Around the Avatar
- Adding More Neat Shapes
- Adding a Curved Shape
- Drawing a Quadratic Bézier Curve
- Finalizing the Curve
- Adding Gradient Paint
- Where to Go From Here?
Using CustomPainter
Now that you’ve learned some theory, it’s time to start using the Android Canvas and add some code that will reproduce your drawing in the app.
Implementing the Painter Interface
Start by creating a new class ProfileCardPainter in the starsofscience package. Then replace the whole file content with:
package com.raywenderlich.android.starsofscience
import android.graphics.*
import androidx.annotation.ColorInt
//1
class ProfileCardPainter(
//2
@ColorInt private val color: Int
) : Painter {
//3
override fun paint(canvas: Canvas) {
}
}
Here you:
You’ll write all your drawing code inside this function, which gives you one parameter: The canvas to draw on.
- Define a new class named
ProfileCardPainterthat implements the interfacePainter. - Then in its primary constructor you define the profile color as a class property.
- Finally, you implement
paint(canvas: Canvas).CustomPainterwill call this method whenever the object needs to paint.You’ll write all your drawing code inside this function, which gives you one parameter: The
canvasto draw on.
Rendering With CustomPainter
Go to MainActivity.kt. You’ll find the following line of code in onCreate():
profileCardContainer.setBackgroundColor(R.color.colorPrimary.toColorInt(this))
It sets a background color to the profileCardContainer which is a FrameLayout already defined in XML. You don’t need that line anymore because you want to add your custom shape instead of that solid color.
Replace that line with the following code:
//1
val azureColor = R.color.colorPrimary.toColorInt(this)
val avatarRadius = R.dimen.avatar_radius.resToPx(this)
val avatarMargin = R.dimen.avatar_margin.resToPx(this)
val cardWidth = ViewGroup.LayoutParams.MATCH_PARENT
val cardHeight = R.dimen.profile_card_height.resToPx(this).toInt()
//2
val painter = ProfileCardPainter(
color = azureColor
)
//3
profileCardContainer.addView(
CustomPainter(
context = this,
width = cardWidth,
height = cardHeight,
painter = painter
)
)
Add any missing import by pressing Option+Enter on Mac or Alt+Enter on PC.
In the code above:
- You define the properties of your custom shape: Color, avatar radius, avatar margin, width and height.
- Then, you create a
ProfileCardPainterwith the color you previously defined. - Finally, you add a new
CustomPainteras a subview ofprofileCardContainerby passing all its needed properties:-
contextto create this custom AndroidView. -
widthandheightof the custom shape. -
painterresponsible for all the drawing logic.
-
Build and run the app to see… a pretty ugly card because you haven’t drawn anything yet. Don’t worry, you’ll start drawing something in a moment. :]
Drawing Your First Shape
In this section, you’ll practice with the tools you need to draw in the computer graphics world. They’re a lot like the physical tools you used to draw a circle on a paper. Then, with this knowledge, you’ll draw your first shape!
Drawing and Painting a Rectangle
To draw a rectangle, you need to create a RectF object with the size you want. You then need a Paint object with the color you prefer to start drawing that RectF on the canvas.
RectF is a simple class with four immutable float properties: Left, top, right and bottom. These four numbers represent a rectangle, where:
- Left is the left-most point on the x-axis.
- Top is the top-most point on the y-axis.
- Right is the right-most point on the x-axis.
- Bottom is the bottom-most point on the y-axis.
In this tutorial, you’ll rely on RectF for your shape bounds. You’ll draw each shape inside of and based on a certain RectF.
RectF, like the width and height, based on these four main properties.
In this tutorial, you’ll rely on RectF for your shape bounds. You’ll draw each shape inside of and based on a certain RectF.
In ProfileCardPainter.kt, go to paint() and add the following:
//1
val width = canvas.width.toFloat()
val height = canvas.height.toFloat()
//2
val shapeBounds = RectFFactory.fromLTWH(0f, 0f, width, height)
//3
val paint = Paint()
paint.color = color
//4
canvas.drawRect(shapeBounds, paint)
Add any missing import by pressing Option+Enter on Mac or Alt+Enter on PC.
Here’s what this code defines:
- The
widthandheightof the canvas. -
shapeBoundsis aRectFwith a size that fits the whole area of the canvas by using the factory functionfromLTWH(). -
paintis your paint and its color. - Finally, you draw your
shapeBoundson thecanvasby passing it todrawRect()along with yourpaintfrom the previous line.
Now, build and run the app. See that the card now has a blue rectangle as its background. Hooray, you’ve drawn your first shape! :]
That’s better, but there’s still much room for improvement!
Using a Path to Draw the Profile Card
A path is not a bitmap or raster, and it doesn’t have pixels. It’s an outline that represents a series of smooth lines, arcs or Bézier curves. Using a path makes your shapes scalable and independent of the screen’s resolution.
Path is a powerful class that you can use in many situations. For example, you can clip a bitmap by a path, or you can use a path to draw a custom shape like you’re about to do right now.
Drawing the Profile Card
In this section, you’ll start using the Path class to draw a more complex shape like the blue shape here:
But before you start, you need to do some preparation.
There are a few things you should note in the previous image:
- Black dashed rectangle: Represents the whole canvas.
- Red dashed rectangle: Marks the bounds of the blue shape. It has the same width and height as the canvas, except that you subtract the avatar radius from its height.
- Blue shape: A rectangle with a half circle, an arc of a circle, as a negative space at the bottom center. This arc should have a radius equal to the radius of the avatar.
The image below shows a blue arc that starts at the zero degree angle and sweeps to 90 degrees.
First, get the radius of the avatar. Start by adding a new class property called avatarRadius to your ProfileCardPainter primary constructor:
class ProfileCardPainter(
@ColorInt private val color: Int,
private val avatarRadius: Float
) : Painter {
Then, go to MainActivity.kt and, in onCreate(), pass the avatarRadius to ProfileCardPainter:
val painter = ProfileCardPainter(
color = azureColor,
avatarRadius = avatarRadius
)
Finally, return to ProfileCardPainter.kt and update the shapeBounds by subtracting the avatarRadius from its height in fromLTWH():
val shapeBounds = RectFFactory.fromLTWH(0f, 0f, width, height - avatarRadius)
To see the results build and run the app:
Great! Now the blue background stops halfway down the length of the avatar.





