Android Custom View Tutorial
Custom View and Custom ViewGroup
What is a custom View?
Sometimes you want to show a certain type of data and there is already a suitable view in the basic widget set. But if you want UI customization or a different user interaction, you may need to extend a widget.
Suppose that there were no Button widget in the basic widget set in the Android SDK and you want to make one. You would extend the TextView class to get all the capabilities related to the text like setting text, text color, text size, text style and so on. Then you will start your customization work, to give your new widget the look and feel of a button. this is what happens in the Android SDK the Button class extends the TextView class.
Or you could in theory extend the View class to start from scratch.
What is a custom ViewGroup?
Sometimes you want to group some views into one component to allow them to deal with each other easily through writing some specific code or business logic. You can call that a “compound view”. Compound views give you reusability and modularity.
For example, you may want to build an emotional face view with a sliding bar that the user can slide to the right to make the emotional face happier or slide to left to make it sadder. You may also want to show that state of happiness in a TextView.
You can group those views (ImageView, SeekBar, TextView) into one layout file, then create a new class that extends a layout (e.g. a LinearLayout or a RelativeLayout) and write your business logic in it.
Another reason for implementing a custom ViewGroup is if you want to make your custom ViewGroup align its children in a different and unique way. For example, laying out the children in a circle instead of linearly as in the LinearLayout.
How Android Draws Views
When an Android activity comes up into the foreground, Android asks it for its root view. The root view is the top parent of the layout hierarchy. Android then starts drawing the whole view hierarchy.
Android draws the hierarchy starting from the top parent, then its children, and if one of the children is also a ViewGroup, Android will draw its children before drawing the second child. So it’s a depth-first traversal.
Android draws the children of a ViewGroup according to the index of the child (its position in the XML file), so the view which you added first will be drawn first.
Android draws the layout hierarchy in three stages:
- Measuring stage: each view must measure itself.
- Layout stage: each ViewGroup finds the right position for its children on the screen by using the child size and also by following the layout rules.
- Drawing stage: after measuring and positioning all of the views, each view happily draws itself. :]
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:
-
constructor(context: Context)
To create a new View instance from Kotlin code, it needs the Activity context. -
constructor(context: Context, attrs: AttributeSet)
To create a new View instance from XML. -
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int)
To create a new view instance from XML with a style from theme attribute. -
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:
- Set the paint color to the faceColor and make it fill the drawing area.
- Calculate a radius for a circle which you want to draw as the face background.
- Draw the background circle with a center of
(x,y), wherexandyare equal to the half of size, and with the calculatedradius. - Change the
paintcolor to theborderColorand make it just draw a border around the drawing area by setting the style to STROKE - Draw a border with the same center but with a radius shorter than the previous
radiusby theborderWidth.
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:
- Set the
paintcolor to theeyesColorand make it fill the drawing area. - Create a RectF object with
left, top, right and bottomusing 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. - 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:
- Set the starting point of the path to
(x0,y0)by using themoveTo()method where:
-
x0is equal to 22% of the size. -
y0is equal to 70% of the size.
(x1,y1) that ends with (x2,y2) where:
-
x1is equal to 50% of the size. -
y1is equal to 80% of the size. -
x2is equal to 78% of the size. -
y2is equal to 70% of the size.
(x2,y2) and through (x3,y3) and that ends with (x0,y0) where:
-
x3is equal to 50% of the size. -
y3is equal to 90% of the size. -
x0is equal to 22% of the size. -
y0is 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:
- Calculate the smaller dimension of your view
- 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:

Creating Custom XML Attributes
To create a new XML attribute go to res/values and create new values resource file named attrs.xml. Add the following lines to the file:
<!--1-->
<declare-styleable name="EmotionalFaceView">
<!--2-->
<attr name="faceColor" format="color" />
<attr name="eyesColor" format="color" />
<attr name="mouthColor" format="color" />
<attr name="borderColor" format="color" />
<attr name="borderWidth" format="dimension" />
<attr name="state" format="enum">
<enum name="happy" value="0" />
<enum name="sad" value="1" />
</attr>
</declare-styleable>
Here you:
- Open the
declare-styleabletag and set thenameattribute to your custom view class name. - Add new attributes with different names and set their
formatto a suitable format.
Go to res/layout/activity_main.xml and add the following new views to the RelativeLayout:
<com.raywenderlich.emotionalface.EmotionalFaceView
android:id="@+id/happyButton"
android:layout_width="@dimen/face_button_dimen"
android:layout_height="@dimen/face_button_dimen"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
app:borderColor="@color/white"
app:eyesColor="@color/white"
app:faceColor="@color/red"
app:mouthColor="@color/white"
app:state="happy" />
<com.raywenderlich.emotionalface.EmotionalFaceView
android:id="@+id/sadButton"
android:layout_width="@dimen/face_button_dimen"
android:layout_height="@dimen/face_button_dimen"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
app:borderColor="@color/black"
app:eyesColor="@color/black"
app:faceColor="@color/light_grey"
app:mouthColor="@color/black"
app:state="sad" />
You have added two EmotionalFaceView objects to the layout, and are using the new custom XML attributes. This proves the reusability concept for the custom view.
The first view has a happy state and the second view has a sad state. You will use both of them later to act as buttons with different themes and different happiness states, and
Build and run the app, and you should see a screen like this:

As you can see, the new XML attributes have no effect yet on the EmotionalFaceView. In order to receive the values of the XML attributes and to use them in the EmotionalFaceView class, update all the lines of code setting up the properties above onDraw() to be:
// 1
companion object {
private const val DEFAULT_FACE_COLOR = Color.YELLOW
private const val DEFAULT_EYES_COLOR = Color.BLACK
private const val DEFAULT_MOUTH_COLOR = Color.BLACK
private const val DEFAULT_BORDER_COLOR = Color.BLACK
private const val DEFAULT_BORDER_WIDTH = 4.0f
const val HAPPY = 0L
const val SAD = 1L
}
// 2
private var faceColor = DEFAULT_FACE_COLOR
private var eyesColor = DEFAULT_EYES_COLOR
private var mouthColor = DEFAULT_MOUTH_COLOR
private var borderColor = DEFAULT_BORDER_COLOR
private var borderWidth = DEFAULT_BORDER_WIDTH
private val paint = Paint()
private val mouthPath = Path()
private var size = 0
// 3
var happinessState = HAPPY
set(state) {
field = state
// 4
invalidate()
}
// 5
init {
paint.isAntiAlias = true
setupAttributes(attrs)
}
private fun setupAttributes(attrs: AttributeSet?) {
// 6
// Obtain a typed array of attributes
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.EmotionalFaceView,
0, 0)
// 7
// Extract custom attributes into member variables
happinessState = typedArray.getInt(R.styleable.EmotionalFaceView_state, HAPPY.toInt()).toLong()
faceColor = typedArray.getColor(R.styleable.EmotionalFaceView_faceColor, DEFAULT_FACE_COLOR)
eyesColor = typedArray.getColor(R.styleable.EmotionalFaceView_eyesColor, DEFAULT_EYES_COLOR)
mouthColor = typedArray.getColor(R.styleable.EmotionalFaceView_mouthColor, DEFAULT_MOUTH_COLOR)
borderColor = typedArray.getColor(R.styleable.EmotionalFaceView_borderColor,
DEFAULT_BORDER_COLOR)
borderWidth = typedArray.getDimension(R.styleable.EmotionalFaceView_borderWidth,
DEFAULT_BORDER_WIDTH)
// 8
// TypedArray objects are shared and must be recycled.
typedArray.recycle()
}
Here you:
- Add two constants, one for the
HAPPYstate and one for theSADstate. - Setup default values of the XML attribute properties, in case a user of the custom view does not set one of them
- Add a new property called
happinessStatefor the face happiness state. - Call the
invalidate()method in theset happinessStatemethod. Theinvalidate()method makes Android redraw the view by callingonDraw(). - Call a new private
setupAttributes()method from theinitblock. - Obtain a typed array of the
XMLattributes - Extract custom attributes into member variables
- Recycle the
typedArrayto make the data associated with it ready for garbage collection.
Build and run the app, and you should see a screen like this:

As you see in the previous screenshot, the happinessState still has no effect, and both of the EmotionalFaceView buttons are happy.
At the beginning of the drawMouth() method, add the following line
mouthPath.reset()
This will reset the path and remove any old path before drawing a new path, to avoid drawing the mouth more than one time while Android calls the onDraw() method again and again.
You want to make the face happy or sad, according to the state, in drawMouth(). Replace the mouthPath() drawing with the following lines of code:
if (happinessState == HAPPY) {
// 1
mouthPath.quadTo(size * 0.5f, size * 0.80f, size * 0.78f, size * 0.7f)
mouthPath.quadTo(size * 0.5f, size * 0.90f, size * 0.22f, size * 0.7f)
} else {
// 2
mouthPath.quadTo(size * 0.5f, size * 0.50f, size * 0.78f, size * 0.7f)
mouthPath.quadTo(size * 0.5f, size * 0.60f, size * 0.22f, size * 0.7f)
}
Here you:
- Draw a happy mouth path by using
quadTo()method as you learned before. - Draw a sad mouth path.
The whole drawMouth() method will be like this
private fun drawMouth(canvas: Canvas) {
// Clear
mouthPath.reset()
mouthPath.moveTo(size * 0.22f, size * 0.7f)
if (happinessState == HAPPY) {
// Happy mouth path
mouthPath.quadTo(size * 0.5f, size * 0.80f, size * 0.78f, size * 0.7f)
mouthPath.quadTo(size * 0.5f, size * 0.90f, size * 0.22f, size * 0.7f)
} else {
// Sad mouth path
mouthPath.quadTo(size * 0.5f, size * 0.50f, size * 0.78f, size * 0.7f)
mouthPath.quadTo(size * 0.5f, size * 0.60f, size * 0.22f, size * 0.7f)
}
paint.color = mouthColor
paint.style = Paint.Style.FILL
// Draw mouth path
canvas.drawPath(mouthPath, paint)
}
Build and run the app, and you should see the top right button become a sad face, like the following screenshot:

User Interaction
You can let your user change the happiness state of the center emotional face view by clicking on the top left button to make it happy or by clicking on the top right button to make it sad. First, add the following line of code to the MainActivity import statements:
import kotlinx.android.synthetic.main.activity_main.*
Kotlin Android Extensions provide a handy way for view binding by importing all widgets in the layout in one go. This allows avoiding the use of findViewById(), which is a source of potential bugs and is hard to read and support.
Now add the following click listeners to onCreate() in MainActivity:
// 1
happyButton.setOnClickListener({
emotionalFaceView.happinessState = EmotionalFaceView.HAPPY
})
// 2
sadButton.setOnClickListener({
emotionalFaceView.happinessState = EmotionalFaceView.SAD
})
Here you:
- Set the
emotionalFaceView‘shappinessStateto HAPPY when the user clicks on the happy button. - Set the
emotionalFaceView‘shappinessStateto SAD when the user clicks on the sad button.
Build and run the app, and click on the both of buttons to change the happiness state:

Saving View State
You can save your view state in case there is any change in the device configuration, e.g., orientation, by overriding the onSaveInstanceState() and onRestoreInstanceState() methods.
Add the following method overrides to EmotionalFaceView:
override fun onSaveInstanceState(): Parcelable {
// 1
val bundle = Bundle()
// 2
bundle.putLong("happinessState", happinessState)
// 3
bundle.putParcelable("superState", super.onSaveInstanceState())
return bundle
}
override fun onRestoreInstanceState(state: Parcelable) {
// 4
var viewState = state
if (viewState is Bundle) {
// 5
happinessState = viewState.getLong("happinessState", HAPPY)
// 6
viewState = viewState.getParcelable("superState")
}
super.onRestoreInstanceState(viewState)
}
Here you:
- Create anew
Bundleobject to put your data into. - Put the happiness
statevalue into the bundle. - Put the state coming from the superclass, in order to not lose any data saved by the superclass, then return the
bundle. - Check the type of the
Parcelableto cast it to aBundleobject. - Get the
happinessStatevalue. - Get the superstate then pass it to the super method.
Build and run the app, change the happiness state to sad, and change the orientation of your device. The center face should remain sad after device rotation:

Where To Go From Here?
Yes! You have created your own custom view :]
You can download the completed project using the download button at the top or bottom of this tutorial.
During this tutorial you:
- Drew a circle, oval and a path on a canvas. You can learn more about custom drawing here.
- Made the custom view responsive.
- Created new XML attributes for the custom view.
- Saved the view state.
- Reused the custom view for different use cases.
You can make your custom view even more interactive by detecting special gestures; check for more info here.
Adding animation to your custom view can enhance the UX strongly. Check out Android Animation Tutorial with Kotlin.
You can also learn more about drawable animation and Canvas and Drawables.
Feel free to share your feedback or ask any questions in the comments below or in the forums. Thanks!
