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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Adding Negative Space Around the Avatar

Next, you’ll add some negative space to the blue shape to set it apart from the avatar. Add a new function called drawBackground() to ProfileCardPainter:


private fun drawBackground(canvas: Canvas, bounds: RectF, avatarBounds: RectF) {
  //1
  val paint = Paint()
  paint.color = color

  //2
  val backgroundPath = Path().apply {
    // 3
    moveTo(bounds.left, bounds.top)
    // 4
    lineTo(bounds.bottomLeft.x, bounds.bottomLeft.y)
    // 5
    lineTo(avatarBounds.centerLeft.x, avatarBounds.centerLeft.y)
    // 6
    arcTo(avatarBounds, -180f, 180f, false)
    // 7
    lineTo(bounds.bottomRight.x, bounds.bottomRight.y)
    // 8
    lineTo(bounds.topRight.x, bounds.topRight.y)
    // 9
    close() 
  }

  //10
  canvas.drawPath(backgroundPath, paint);
}

Add any missing import by pressing Option+Enter on Mac or Alt+Enter on PC. To import all the extension functions you need for RectF in a row, add the following import:

import com.raywenderlich.android.starsofscience.utils.*

This diagram illustrates the proper coordinates for each point you need to build the path.

Custom path

In the previous code:

  1. You create a Paint object and set its color.
  2. Then, you create a Path object.
  3. You move to the top-left corner, P1, without drawing a line. This is like moving a pencil to a starting point without touching the paper.
  4. Next, you add a straight line that starts at P1 and ends at P2.
  5. Then, you add a straight line that starts at P2 and ends at P3: The point at the edge of where you will start drawing the arc.
  6. Then, starting from P3, add an arc in the upper half region of the avatar bounds: The arc starts from the angle -180 degrees and sweeps by 180 degrees ending at P4.
    You pass false as the last parameter to prevent starting a new sub-path for the arc. This tells Android that you want the arc on the same path.
  7. Next, you add a straight line that starts from the current point and ends at P5 at the bottom-right corner.
  8. You finish by adding a straight line that starts from the current point P5 and ends at the given point P6 at the top-right corner.
  9. Then you close the path by adding a straight line that starts at the current point P6 and ends at the beginning point on the path, P1.
  10. Finally, you draw the backgroundPath on the canvas by passing it to drawPath() with paint.

In the previous code, you can collapse lines five and six in a single line. Do you know how? You can find the solution in the spoiler below.

[spoiler title=”Solution”]
You can collapse lines five and six by leaving only line six.

arcTo(avatarBounds, -180f, 180f, false)

The official documentation of

arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)

states: “If the start of the path is different from the path’s current last point, then an automatic lineTo() is added to connect the current contour to the start of the arc.”
[/spoiler]

Phew! That was a lot of code, but it was worth the effort!

Creating the Rectangle Around the Avatar

In ProfileCardPainter, go to paint() and replace the last three lines:

    val paint = Paint()
    paint.color = color
    canvas.drawRect(shapeBounds, paint)

with the following code to create a new RectF around the avatar:

//1
val centerAvatar = PointF(shapeBounds.centerX(), shapeBounds.bottom)
//2
val avatarBounds = RectFFactory.fromCircle(center = centerAvatar, radius = avatarRadius)
//3
drawBackground(canvas, shapeBounds, avatarBounds)

Here’s what this code does:

  1. You create a PointF object for the center point of the avatar, where x is the shapeBounds.centerX() and y is the bottom of the shapeBounds.
  2. Then, you create a RectF object from the avatar circle using fromCircle(). The center is centerAvatar, which you just created, and the radius is the avatarRadius.
  3. Finally, you call drawBackground() and pass the canvas with rest of the parameters to draw your first path.

Build and run the app. You’ll see this:

Initial results for your custom painter

You probably don’t notice the difference yet. Don’t worry, you’ll fix that next.

Adding a Margin Around the Avatar

There is a difference, but you can’t see it because the negative space is exactly equal to the circular avatar’s size. Next, you’ll make that negative space a bit bigger to leave a margin between it and the avatar.

First, get the margin of the avatar. Start by adding one more class property called avatarMargin to your ProfileCardPainter primary constructor, don’t forget the comma at the end of the line above the new code.

class ProfileCardPainter(
    ...
    private val avatarMargin: Float
)

Then, go to MainActivity.kt and, in onCreate(), pass the avatarMargin to the ProfileCardPainter constructor:

val painter = ProfileCardPainter(
  ...
  avatarMargin = avatarMargin
)

Finally, return to ProfileCardPainter.kt and\where you create the avatarBounds in paint, add .inflate(avatarMargin) to the end:

val avatarBounds = RectFFactory.fromCircle(center = centerAvatar, radius = avatarRadius).inflate(avatarMargin)

Calling inflate() on a RectF creates a new RectF object whose left, top, right and bottom edges are moved outwards by the given value. The result is a nice space around the avatar.

To see the margin in action, build and run the app.

Margin around the avatar

Pretty... but ordinary. Next, you'll spice up the background by adding an attractive curved shape.

Adding More Neat Shapes

To enhance your custom shape, you can add some simple decorations like stars or circles in a partially-faded color. For this app, you'll add a more interesting decoration: A curvy shape in gradient colors.

Adding a Curved Shape

Before you start drawing, take a moment to learn about the different types of curves. The Quadratic Bézier Curve and the Cubic Bézier Curve are two commonly used curves.

Quadratic Bézier Curve

Cubic Bézier Curve

  • A quadratic Bézier curve requires three points to draw: A start point, an endpoint and a handle point that pulls the curve towards it.

    Quadratic Bézier Curve

  • A cubic Bézier curve needs four points to draw: A start point, an end point and two handle points that pull the curve towards them.

    Cubic Bézier Curve

Next, you'll use a quadratic Bézier curve to create an interesting background shape.

Drawing a Quadratic Bézier Curve

Start by creating a new function called drawCurvedShape() inside ProfileCardPainter with the following:

private fun drawCurvedShape(canvas: Canvas, bounds: RectF, avatarBounds: RectF) {
  //1
  val paint = Paint()
  paint.color = color.darkerShade()

  //2
  val handlePoint = PointF(bounds.left + (bounds.width() * 0.25f), bounds.top)

  //3
  val curvePath = Path().apply {
    //4
    moveTo(bounds.bottomLeft.x, bounds.bottomLeft.y)
    //5
    lineTo(avatarBounds.centerLeft.x, avatarBounds.centerLeft.y)
    //6
    arcTo(avatarBounds, -180f, 180f, false)
    //7
    lineTo(bounds.bottomRight.x, bounds.bottomRight.y)
    //8
    lineTo(bounds.topRight.x, bounds.topRight.y)
    //9
    quadTo(handlePoint.x, handlePoint.y, bounds.bottomLeft.x, bounds.bottomLeft.y)
    //10
    close()
  }

  //11
  canvas.drawPath(curvePath, paint)
}

This diagram will help you understand the code you added. Use it as a guide to the proper coordinates for each point you'll build to create the path:

Path for new arc

In the previous code:

  1. You create a Paint object and set its color to a darker shade of the profile color.
  2. Then, you create a handle point at the top left corner of the RectF, shifted to the right by 25% of the width of the RectF. This is P6 in the guide image.
  3. You create a Path object.
  4. Then, you move to the bottom-left corner, P1 in the guide image.
  5. You add a straight line that starts from P1 and ends at P2: The middle point at the edge of the black dashed avatar bounds RectF.
  6. Then, starting from the current point, P2, add an arc in the upper- half region of the avatar bounds: The arc starts from the angle -180 degrees and sweeps by 180 degrees ending in P3.
    You pass false as the last parameter so you don't start a new sub-path for the arc. This tells Android that you want the arc on the same path.
  7. You add a straight line that starts from the current point and ends at the given point, the bottom-right corner. This adds a line from P3 to P4.
  8. Then, you add a straight line that starts from the current point and ends at the given point, the top-right corner, adding a line from P4 to P5.
  9. You add a quadratic Bézier curve that starts from the current point, P5, and ends at the bottom-left corner, P1, using the handle point you created in step two.
  10. Finally, you close the path, even though it's not required this time since you are back at the beginning point on the path.
  11. You draw curvePath on the canvas by passing it to drawPath() along with the paint object.