onMeasure() is a protected method of the android.view.View class that is called by the Android system to determine the size of a View. The system passes two MeasureSpec objects to the method, each containing a measurement mode (EXACTLY, AT_MOST, or UNSPECIFIED) and a size suggested by the parent container. According to the Android Developers Documentation (2026), overriding onMeasure with correct MeasureSpec handling is a mandatory step for all custom Views and ViewGroups that require precise size control.
Key Takeaways
onMeasure(int widthMeasureSpec, int heightMeasureSpec) is a method of the View class that the Android system calls to determine the width and height of a view. The developer overrides this method to specify what size the View should be based on the constraints passed in MeasureSpec. Without correct onMeasure overriding, a custom View may display incorrectly or not appear at all.
The system calls onMeasure during the measure phase of the View lifecycle, which precedes the layout (onLayout) and draw (onDraw) phases. If a View does not override onMeasure, the superclass implementation is used, which sets default sizes based on background drawable or layout_params. Calling super.onMeasure(widthMeasureSpec, heightMeasureSpec) only works for standard View subclasses such as TextView or ImageView.
A key requirement for onMeasure is that the call to setMeasuredDimension(int, int) must be present at the end of the method. If this call is missing, the system throws an IllegalStateException stating that the View did not set measured dimensions. The final dimensions become available through the getMeasuredWidth() and getMeasuredHeight() getters after the measure phase completes.
MeasureSpec is a 32-bit integer where the top 2 bits encode the measurement mode and the lower 30 bits encode the size. The mode determines how free the View is in choosing its own size. Android provides three modes: EXACTLY, AT_MOST, and UNSPECIFIED. Each mode dictates different processing logic in onMeasure.
| MeasureSpec Mode | Value | Behavior |
|---|---|---|
| EXACTLY | Parent specified an exact size | The View must fit exactly into the given size if it does not want to exceed boundaries |
| AT_MOST | Parent set a maximum size | The View can choose any size from 0 to the given maximum |
| UNSPECIFIED | Parent imposes no constraints | The View can choose any desired size with no upper limit |
To extract the mode and size from MeasureSpec, static methods of the MeasureSpec class are used: MeasureSpec.getMode(int) returns one of the three modes (EXACTLY, AT_MOST, UNSPECIFIED), and MeasureSpec.getSize(int) returns the numeric size in pixels. To create a custom MeasureSpec, use MeasureSpec.makeMeasureSpec(int size, int mode). These three methods cover all scenarios for working with sizes in onMeasure.
The standard pattern for handling MeasureSpec: if the mode is EXACTLY, use the given size as final; if AT_MOST, choose the minimum of the desired size (the View's content) and the given maximum; if UNSPECIFIED, use the desired View size without constraints. This pattern ensures correct behavior under any parent constraints.
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val desiredWidth = 200
val desiredHeight = 100
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val width = when (widthMode) {
MeasureSpec.EXACTLY -> widthSize
MeasureSpec.AT_MOST -> minOf(desiredWidth, widthSize)
else -> desiredWidth
}
val height = when (heightMode) {
MeasureSpec.EXACTLY -> heightSize
MeasureSpec.AT_MOST -> minOf(desiredHeight, heightSize)
else -> desiredHeight
}
setMeasuredDimension(width, height)
}
To simplify the standard logic, Android provides the resolveSizeAndState method, which takes the desired size, MeasureSpec, and returns the final size with the correct mode. This method implements the pattern described above in a single line of code. The resolveSize(int size, int measureSpec) function is also available, returning a clean size without state bits.
Android uses a two-pass measurement algorithm that ensures every View in the hierarchy gets correct dimensions considering parent constraints and child preferences. On the first pass, the parent passes MeasureSpec with constraints to child Views, and child Views compute their desired sizes. On the second pass, the parent makes the final size decision.
For ViewGroup, the measurement process is more complex: the parent must first measure all its children, then determine its own size based on their sizes. Calling measureChildren(int widthMeasureSpec, int heightMeasureSpec) iterates through all child Views and calls measure(child, childWidthSpec, childHeightSpec) for each. After measuring all children, the ViewGroup calls setMeasuredDimension with its own dimensions.
An important nuance: the measure method (public, final) cannot be overridden — instead, onMeasure is overridden. This ensures the system can perform housekeeping tasks before and after onMeasure, such as checking size changes and calculating the dirty area for subsequent drawing. If a View has fixed dimensions, overriding onMeasure may not be necessary.
MeasureSpec includes not only the size and mode but also state bits, accessible via MeasureSpec.getMode(). After calling setMeasuredDimension, the state becomes part of the View's measured dimensions and can be checked via getMeasuredState(). This is used in ScrollView and other scrollable containers for correctly passing constraints to children.
Let us examine a practical example of creating a custom View with onMeasure overridden for square display. The SquareView class extends View and ensures that width and height are always equal, regardless of the passed MeasureSpec. In onMeasure, the minimum side is determined and the square size is set.
class SquareView(context: Context)
: View(context) {
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val widthSize =
MeasureSpec.getSize(widthMeasureSpec)
val heightSize =
MeasureSpec.getSize(heightMeasureSpec)
val size = minOf(widthSize, heightSize)
setMeasuredDimension(size, size)
}
}
ViewGroup requires more complex onMeasure logic because children must be measured first, then the ViewGroup's own size is determined. The CascadeLayout example distributes children in a cascade with an offset. After measuring all children via measureChildWithMargins, the total width and height are calculated.
class CascadeLayout(context: Context)
: ViewGroup(context) {
private val cascadeOffset = 40
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
var maxWidth = 0
var totalHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChildWithMargins(child,
widthMeasureSpec,
cascadeOffset * i,
heightMeasureSpec, 0)
maxWidth = maxOf(maxWidth,
child.measuredWidth +
cascadeOffset * i)
totalHeight += child.measuredHeight
}
setMeasuredDimension(
resolveSize(maxWidth, widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec))
}
override fun generateLayoutParams(attrs: AttributeSet?)
: LayoutParams = MarginLayoutParams(context, attrs)
override fun onLayout(changed: Boolean,
l: Int, t: Int,
r: Int, b: Int) {
var top = t
for (i in 0 until childCount) {
val child = getChildAt(i)
val left = l + cascadeOffset * i
child.layout(left, top,
left + child.measuredWidth,
top + child.measuredHeight)
top += child.measuredHeight
}
}
}
Missing setMeasuredDimension call is the most common mistake. If a developer overrides onMeasure but does not call setMeasuredDimension, the application crashes with IllegalStateException. This is especially common when the method has conditional branches and one branch lacks the call. Every code branch in onMeasure must end with a call to setMeasuredDimension.
Ignoring AT_MOST mode is the second most frequent mistake. If a View in AT_MOST mode always uses the passed size instead of calculating based on content, the parent container cannot properly distribute space. For example, a TextView in AT_MOST should calculate the text width and use the minimum of the desired and passed widths. Ignoring AT_MOST causes the View to occupy all available space even with small content.
Creating objects inside onMeasure is a classic performance mistake. Since onMeasure can be called multiple times (on every layout request), creating objects (Paint, Rect, String) inside this method clutters memory and triggers garbage collection. All objects should be created once in the View constructor, and only size calculation logic should run in onMeasure. The same rule applies to onDraw and onLayout.
measureChildWithMargins is a protected ViewGroup method that measures a single child View considering its MarginLayoutParams. The method accepts the parent MeasureSpec and accumulated width and height offsets. It automatically adjusts the MeasureSpec for the child View by subtracting parent paddings and child margins, then passes the adjusted MeasureSpec to child.measure().
For advanced measurement logic, a ViewGroup can override measureChild(View child, int parentWidthSpec, int parentHeightSpec) or work directly with MeasureSpec for each child. For example, LinearLayout in onMeasure iterates through all child Views, measures each considering its layout_weight, and distributes the remaining space proportionally. This approach allows implementing arbitrary layout algorithms.
Caching measurement results via the measure cache mechanism is available through the setMeasureWithLargestChildEnabled flag in certain ViewGroups. However, in most cases onMeasure is called again on any layout change, and caching does not apply. In custom ViewGroups, it is recommended to minimize computations in onMeasure rather than rely on caching.
Frequently Asked Questions
Yes, if the custom View inherits directly from the View class. If it inherits from TextView, ImageView, or Button with their standard sizes, onMeasure can be left unchanged. For ViewGroup, overriding onMeasure is always required — otherwise children will not be correctly measured.
The Android system throws IllegalStateException with the message “The View did not call setMeasuredDimension”. This exception occurs in the measure() method after onMeasure completes, if the final dimensions remained zero. The exception crashes the application if not handled via try-catch.
getMeasuredWidth() returns the size set in onMeasure (measurement phase). getWidth() returns the actual size the View received in onLayout after all positioning adjustments. For most Views these values match, but in custom ViewGroups they may differ.
No, onMeasure is intended exclusively for size calculation. Changing state, starting animations, networking, or updating data in this method violates Android architecture and may lead to recursive measure calls, since state changes can trigger requestLayout.
ConstraintLayout manages child measurement independently based on defined constraints. If a custom View inside ConstraintLayout overrides onMeasure, it must correctly handle the MeasureSpec passed from ConstraintLayout, otherwise constraints may not work. ConstraintLayout uses a two-pass algorithm with its own WidgetContainer for calculation.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also