All Articles

Custom Views Lifecycle and handling onMeasure()

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.

View lifecycle

Image origin

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.