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
ProfileCardPainter
that implements the interfacePainter
. - Then in its primary constructor you define the profile color as a class property.
- Finally, you implement
paint(canvas: Canvas)
.CustomPainter
will 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
canvas
to 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
ProfileCardPainter
with the color you previously defined. - Finally, you add a new
CustomPainter
as a subview ofprofileCardContainer
by passing all its needed properties:-
context
to create this custom AndroidView
. -
width
andheight
of the custom shape. -
painter
responsible 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
width
andheight
of the canvas. -
shapeBounds
is aRectF
with a size that fits the whole area of the canvas by using the factory functionfromLTWH()
. -
paint
is your paint and its color. - Finally, you draw your
shapeBounds
on thecanvas
by passing it todrawRect()
along with yourpaint
from 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.