When building your own view, a custom view, there are some items you need to consider. First, let’s introduce the lifecycle of a View.
The most important functions you need to worry about are
onMeasure()
onLayout()
onDraw()
onMeasure
onMeasure provides a way for you to negotiate the width and height of your view.
If you don’t override it, your view will behave the same way when match_parent
and wrap_content
is used to define it’s size. Your view will also respect the values passed for height and width.
If you choose to have more control on defining the size of the view then you probably need to override it and not call super.onMeasure()
. Instead you would want to call setMeasuredDimension(width,height)
.
Overriding onMeasure()
When onMeasure is called you get widthMeasureSpec
and heightMeasureSpec
. This Spec is a way for the parent view to inform you about the requested size and size mode for your view. A size mode is a constraint the parent view sets for its child. It’s either
-
UNSPECIFIED
: The parent has not imposed any constraint on the child. It can be whatever size it wants. -
EXACTLY
: The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be. -
AT_MOST
: The child can be as large as it wants up to the specified size.
With that in mind, here’s an example on how to implement onMeasure()
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val requestedWidth = MeasureSpec.getSize(widthMeasureSpec)
val requestedWidthMode = MeasureSpec.getMode(widthMeasureSpec)
val requestedHeight = MeasureSpec.getSize(heightMeasureSpec)
val requestedHeightMode = MeasureSpec.getMode(heightMeasureSpec)
val desiredWidth: Int = //TODO("Define your desired width")
val desiredHeight: Int = //TODO("Define your desired height")
val width = when (requestedWidthMode) {
MeasureSpec.EXACTLY -> requestedWidth
MeasureSpec.UNSPECIFIED -> desiredWidth
else -> Math.min(requestedWidth, desiredWidth)
}
val height = when (requestedHeightMode) {
MeasureSpec.EXACTLY -> requestedHeight
MeasureSpec.UNSPECIFIED -> desiredHeight
else -> Math.min(requestedHeight, desiredHeight)
}
setMeasuredDimension(width, height)
}
This is setting the size to the requested size if the mode is EXACTLY
as we have to respect the parent’s requested mode. If the mode is UNSPECIFIED
then we are free to use whatever size we want, which is usually the case with wrap_content
. If the mode is AT_MOST
then we compare desired
and requested
sizes and use the lowest of value.
onLayout
The parent uses onLayout()
to notify your view of its position. You get to know if the position has changed and left, top, right and bottom position of your view. You should use to calculate your drawing width and height.
Remember that what happens in onMeasure()
affects the positions you will get from your parent. You should always calculate your drawing size here before proceeding to draw your view.
onDraw
This is where all the drawing happens. It’s as simple as you get an instance of Canvas
object and you are free to draw what you want.
Performance considerations
Your onDraw()
implementation should be efficient. You should avoid any long loops or creating new instances. Google’s official documentation says “your app should render frames in under 16ms to achieve 60 frames per second”.
Checkout this piece of documentation to know more on tackling slow rendering.