Android Custom View Tutorial

Create an Android Custom View in Kotlin and learn how to draw shapes on the canvas, make views responsive, create new XML attributes, and save view state. By Ahmed Tarek.

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

Creating a custom view

It’s finally time to start making a custom view yourself!

Start by creating a new Kotlin class and in the main app package and name it EmotionalFaceView. Make it inherit from the View class:

class EmotionalFaceView : View

Now if you hover on the word View you will get a message:
“This type has a constructor, and thus must be initialized here”

Android View Class Constructors

View has four constructors and you will need to override one of them at least to start your customization. Check out all of them to pick the suitable one for the tutorial:

  1. constructor(context: Context)
    To create a new View instance from Kotlin code, it needs the Activity context.
  2. constructor(context: Context, attrs: AttributeSet)
    To create a new View instance from XML.
  3. constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int)
    To create a new view instance from XML with a style from theme attribute.
  4. constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int)
    To create a new view instance from XML with a style from theme attribute and/or style resource.

Pick the second constructor to create your new instance from XML, you can override the constructor in the class body as:

constructor(context: Context, attrs: AttributeSet): super(context, attrs)

Or, make it the primary constructor using:

class EmotionalFaceView(context: Context, attrs: AttributeSet) : View(context, attrs)

Now you can add your custom view at the center of the layout and below the TextView, by adding the following lines to activity_main.xml

<!--Full path for the cusom view -->
<com.raywenderlich.emotionalface.EmotionalFaceView
   android:id="@+id/emotionalFaceView"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:layout_centerInParent="true"
   android:layout_below="@+id/textView" />

Congrats! You have created a custom view and you have added it to the layout! But it still has no your special customization.

Build and run the project, and as you expect there is no change in the UI, but don’t worry: you will start the fun part right now :]

Drawing on Canvas

Prepare your painting tools in EmotionalFaceView by declaring a Paint property for coloring and styling, and some colors:

// Paint object for coloring and styling
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
// Some colors for the face background, eyes and mouth.
private var faceColor = Color.YELLOW
private var eyesColor = Color.BLACK
private var mouthColor = Color.BLACK
private var borderColor = Color.BLACK
// Face border width in pixels
private var borderWidth = 4.0f
// View size in pixels
private var size = 320

Now start drawing by overriding the onDraw() method from the parent class. Android invokes onDraw() for you and pass a canvas for drawing:

override fun onDraw(canvas: Canvas) {
  // call the super method to keep any drawing from the parent side.
  super.onDraw(canvas)
}

Create three new methods for drawing the happy face. All of them have a Canvas object as a parameter. Call them from onDraw():

override fun onDraw(canvas: Canvas) {
  // call the super method to keep any drawing from the parent side.
  super.onDraw(canvas)

  drawFaceBackground(canvas)
  drawEyes(canvas)
  drawMouth(canvas)
}

private fun drawFaceBackground(canvas: Canvas) {
}

private fun drawEyes(canvas: Canvas) {
}

private fun drawMouth(canvas: Canvas) {
}

Draw the face background
Add the following code to drawFaceBackground():

 // 1
 paint.color = faceColor
 paint.style = Paint.Style.FILL

 // 2
 val radius = size / 2f

 // 3
 canvas.drawCircle(size / 2f, size / 2f, radius, paint)

 // 4
 paint.color = borderColor
 paint.style = Paint.Style.STROKE
 paint.strokeWidth = borderWidth

 // 5
 canvas.drawCircle(size / 2f, size / 2f, radius - borderWidth / 2f, paint)

Here you:

  1. Set the paint color to the faceColor and make it fill the drawing area.
  2. Calculate a radius for a circle which you want to draw as the face background.
  3. Draw the background circle with a center of (x,y), where x and y are equal to the half of size, and with the calculated radius.
  4. Change the paint color to the borderColor and make it just draw a border around the drawing area by setting the style to STROKE
  5. Draw a border with the same center but with a radius shorter than the previous radius by the borderWidth.

Build and run the app, and you should see a screen like this:

Draw the Eyes
Add the following code to drawEyes():

// 1
paint.color = eyesColor
paint.style = Paint.Style.FILL

// 2
 val leftEyeRect = RectF(size * 0.32f, size * 0.23f, size * 0.43f, size * 0.50f)

canvas.drawOval(leftEyeRect, paint)

// 3
val rightEyeRect = RectF(size * 0.57f, size * 0.23f, size * 0.68f, size * 0.50f)

canvas.drawOval(rightEyeRect, paint)

Here you:

  1. Set the paint color to the eyesColor and make it fill the drawing area.
  2. Create a RectF object with left, top, right and bottom using the following percentages of the size: (32%, 23%, 43%, 50%). Then you draw the left eye by drawing an oval with the created RectF. For more info about RectF, check the docs.
  3. Do the same as the last step but with the following percentages of the size: (57%, 23%, 68%, 50%)

Build and run the app, and you should see a screen like this:

Draw the mouth

To draw curved paths on a canvas you need to create a path object. Add the following property to the EmotionalFaceView class:

private val mouthPath = Path()

After creating the Path object, set the curving instructions for it by adding the following code to the drawMouth() :

// 1
mouthPath.moveTo(size * 0.22f, size * 0.7f)
// 2
mouthPath.quadTo(size * 0.50f, size * 0.80f, size * 0.78f, size * 0.70f)
// 3
mouthPath.quadTo(size * 0.50f, size * 0.90f, size * 0.22f, size * 0.70f)
// 4
paint.color = mouthColor
paint.style = Paint.Style.FILL
// 5
canvas.drawPath(mouthPath, paint)

Here you:

  1. Set the starting point of the path to (x0,y0) by using the moveTo() method where:
  • x0 is equal to 22% of the size.
  • y0 is equal to 70% of the size.

Build and run the app, and you should see a screen like this:

Responsive View

Currently, your custom view has a fixed size, but you want it to be responsive and fit its parent. Also, you want the happy face to always be a circle, not an oval shape.

Android measures the view width and heigh. You can get these values by using measuredWidth, measuredHeight.

Override the onMeasure() method to provide an accurate and efficient measurement of the view contents:


override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {      super.onMeasure(widthMeasureSpec, heightMeasureSpec)

}

Add the following lines of code to onMeasure():

// 1
size = Math.min(measuredWidth, measuredHeight)
// 2
setMeasuredDimension(size, size)

Here you:

  1. Calculate the smaller dimension of your view
  2. Use setMeasuredDimension(int, int) to store the measured width and measured height of the view, in this case making your view width and height equivalent.

Build and run the app, and you should see a screen like this:

Ahmed Tarek

Contributors

Ahmed Tarek

Author

Arun Sasidharan

Tech Editor

Joe Howard

Final Pass Editor

Over 300 content creators. Join our team.