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.
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
Android Custom View Tutorial
25 mins
- Getting Started
- Working with the Basic Widgets
- Working with Views in Kotlin
- Working with Views in XML
- Android Views
- Custom View and Custom ViewGroup
- How Android Draws Views
- Creating a custom view
- Android View Class Constructors
- Drawing on Canvas
- Responsive View
- Creating Custom XML Attributes
- User Interaction
- Saving View State
- Where To Go From Here?
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)
, wherex
andy
are equal to the half of size, and with the calculatedradius
. - Change the
paint
color to theborderColor
and 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
radius
by 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
paint
color to theeyesColor
and make it fill the drawing area. - 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. - 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:
-
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:
- 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: