onLayout() is a method of the ViewGroup class that determines the positions and sizes of child Views on the coordinate plane of the parent container. The Android system calls onLayout after the measurement phase (onMeasure), when the measured width and height are already known for each child View. According to Android Developers Documentation (2026), onLayout is a mandatory method to override in any custom ViewGroup, since the standard ViewGroup implementation does not perform automatic child positioning.
Key Takeaways
onLayout(boolean changed, int l, int t, int r, int b) is a protected method of the ViewGroup class that is called by the system to position child Views inside the parent container. The developer overrides this method when creating a custom ViewGroup with a non-standard element arrangement: cascading, grid, staggered, or by arbitrary coordinates. Each child View receives its final boundaries through a call to child.layout().
The changed parameter indicates whether the position or size of the ViewGroup itself has changed since the last layout. If changed is true, all child elements likely also need repositioning. The parameters l, t, r, b are the coordinates of the top-left and bottom-right corners of the ViewGroup in its parent's coordinate system. Inside onLayout, the developer uses these values as starting coordinates for arranging children.
ViewGroup is the only class that overrides onLayout. A regular View (not a ViewGroup) has no child elements and does not need onLayout — its positioning is handled by the parent container. Even if a regular View overrides onLayout, the system will not call it. This is a fundamental difference from onMeasure, which is called for any View.
The layout phase begins with a call to the public method layout(int l, int t, int r, int b) on the root View. This method sets the final coordinates of the View itself and calls onLayout if the View is a ViewGroup. Then onLayout recursively calls child.layout() for each child element, and the process repeats down the hierarchy. Thus, layout propagates from the root to the leaves.
Before calling onLayout, the system checks whether the View's dimensions have changed compared to the previous cycle. If the dimensions have not changed and requestLayout has not been called, onLayout may not be called — the system uses the results of the previous layout. This is an optimization that prevents unnecessary position recalculations during animations or scrolling, when only the content changes but not the dimensions.
requestLayout() is a View method that notifies the system that the View's layout is outdated and needs to be recalculated. Calling requestLayout triggers a full cycle: first onMeasure is called, then onLayout, then onDraw. Unlike invalidate, which only triggers redrawing, requestLayout triggers a full recalculation of dimensions and positions. Excessive calls to requestLayout are a common cause of performance issues.
l (left) — the X-coordinate of the left edge of the ViewGroup in its parent's coordinate system. t (top) — the Y-coordinate of the top edge. r (right) — the X-coordinate of the right edge. b (bottom) — the Y-coordinate of the bottom edge. The width of the ViewGroup is computed as r - l, the height as b - t. These coordinates already include all padding of the ViewGroup itself.
Inside onLayout, the developer calls child.layout(int childLeft, int childTop, int childRight, int childBottom) for each child View. The coordinates passed to child.layout must be in the parent ViewGroup's coordinate system. Typically childLeft and childTop are calculated taking into account the parent's padding: childLeft = l + paddingLeft + offsetX, childTop = t + paddingTop + offsetY.
| Parameter | Description | Typical Usage |
|---|---|---|
| l (left) | Coordinate of the left edge of the ViewGroup in the parent | Starting point on the X-axis for child elements |
| t (top) | Coordinate of the top edge of the ViewGroup in the parent | Starting point on the Y-axis for child elements |
| r (right) | Coordinate of the right edge of the ViewGroup in the parent | Upper width bound, r - l = getWidth() |
| b (bottom) | Coordinate of the bottom edge of the ViewGroup in the parent | Upper height bound, b - t = getHeight() |
Child coordinates are calculated using the formula: childLeft = l + paddingLeft + (marginLeft if present), childRight = childLeft + child.getMeasuredWidth(). Similarly for the vertical axis: childTop = t + paddingTop + (marginTop), childBottom = childTop + child.getMeasuredHeight(). After computing these four values, child.layout(childLeft, childTop, childRight, childBottom) is called.
Let's create a FlowLayout — a custom ViewGroup that arranges child Views in rows, wrapping elements to a new line when the current row is full. This is a Flexbox with wrap counterpart in a single plane. onLayout iterates through all child Views, calculates the position for each, and calls child.layout() with correct boundaries.
class FlowLayout(context: Context)
: ViewGroup(context) {
private val horizontalSpacing = 12
private val verticalSpacing = 8
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val parentWidth =
MeasureSpec.getSize(widthMeasureSpec)
var rowX = paddingLeft
var rowY = paddingTop
var maxRowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChildWithMargins(child,
widthMeasureSpec, 0,
heightMeasureSpec, 0)
if (rowX + child.measuredWidth >
parentWidth - paddingRight) {
rowX = paddingLeft
rowY += maxRowHeight + verticalSpacing
maxRowHeight = 0
}
rowX += child.measuredWidth +
horizontalSpacing
maxRowHeight = maxOf(maxRowHeight,
child.measuredHeight)
}
val totalHeight = rowY + maxRowHeight +
paddingBottom
setMeasuredDimension(
resolveSize(parentWidth, widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec))
}
override fun onLayout(changed: Boolean,
l: Int, t: Int,
r: Int, b: Int) {
val parentWidth = r - l
var rowX = paddingLeft
var rowY = paddingTop
var maxRowHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
val cw = child.measuredWidth
val ch = child.measuredHeight
if (rowX + cw >
parentWidth - paddingRight) {
rowX = paddingLeft
rowY += maxRowHeight + verticalSpacing
maxRowHeight = 0
}
child.layout(rowX, rowY,
rowX + cw, rowY + ch)
rowX += cw + horizontalSpacing
maxRowHeight =
maxOf(maxRowHeight, ch)
}
}
override fun generateLayoutParams(attrs: AttributeSet?)
: LayoutParams =
MarginLayoutParams(context, attrs)
}
onMeasure and onLayout are two sequential phases of the View lifecycle that perform fundamentally different tasks. onMeasure determines the desired (measured) dimensions of a View, while onLayout sets the actual (final) coordinates and dimensions. The key difference: in onMeasure, dimensions may be intermediate and later adjusted by the parent, while in onLayout the final position of each child View is fixed.
onMeasure is called for every View, including leaf Views (TextView, ImageView, Button). onLayout is called only for ViewGroup. This is because positioning is the responsibility of the parent container, not the View itself. A leaf View receives its position through layout() called from the parent's onLayout.
getMeasuredWidth() and getMeasuredHeight() are available after onMeasure, while getWidth() and getHeight() are only available after onLayout. If you access getWidth() inside onMeasure, it will return the value from the previous cycle or zero. Therefore, for calculating dimensions in onMeasure, you should use MeasureSpec and children sequentially.
Positioning without accounting for padding — the first mistake when implementing onLayout. The developer often forgets to add the parent's paddingLeft and paddingTop to the initial coordinates of child Views. As a result, children are displayed at the edge of the ViewGroup, ignoring the padding set via setPadding() or in XML markup. Correct calculation: childLeft = paddingLeft + offsetX.
Calling layout for invisible children — the second common problem. If a ViewGroup contains child Views with visibility set to GONE, they do not need to be positioned — they take up no space. However, onLayout must handle this case correctly by skipping GONE children. For INVISIBLE children, layout still needs to be called — they retain their space even though they are not displayed.
Ignoring the changed parameter — the third mistake. The changed parameter indicates whether the ViewGroup's dimensions or position have changed. If changed == false, cached coordinates can be used without recalculating the layout of all child elements. However, full layout caching is a complex task, and in most implementations onLayout simply recalculates all elements each time. This is acceptable with a small number of children.
Frequently Asked Questions
Yes, it is possible if the ViewGroup uses standard LayoutParams and does not add custom positioning logic. However, the standard onLayout implementation in ViewGroup does not perform any actions — child elements will not be positioned. In practice, all ViewGroup implementations (LinearLayout, RelativeLayout, FrameLayout) override onLayout.
layout() is a public final method of View, called by the system or parent ViewGroup. It sets the coordinates of the View itself and calls onLayout if the View is a ViewGroup. onLayout() is a protected method that the developer overrides for custom arrangement of child elements.
Technically — yes, it can. But this is strongly not recommended, as it leads to infinite recursion: requestLayout → onMeasure → onLayout → requestLayout. If requestLayout is called inside onLayout, the system will throw a StackOverflowError. All dimension changes should be performed before onLayout.
Layout animations (LayoutTransition) intercept changes in child View positions and apply transition animation. When LayoutTransition is enabled, onLayout first sets the final positions, then LayoutTransition animates the movement from the old position to the new one. This requires a correct onLayout implementation with proper final coordinates.
invalidate() triggers only the draw phase (redrawing), without affecting measure and layout. To trigger onLayout, you need to call requestLayout(), which initiates the full cycle: measure → layout → draw. invalidate is more efficient for updating appearance when dimensions and positions do not change.
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