Creating custom layouts in Android gives you full control over view measurement and placement. By extending the ViewGroup class, you can define how child views are measured and positioned, enabling truly flexible layouts beyond what standard containers like LinearLayout or ConstraintLayout offer.
This updated guide on javatechig.com explains how to build custom layouts with proper measurement logic, layout passes, custom attributes, and lifecycle considerations aligned with modern Android practices recommended by Android Developers.
Why Create a Custom Layout?
Standard layouts handle most UI needs, but custom layouts are required when:
- You need specific child arrangement rules
- You want optimized view hierarchies
- Default layouts don’t satisfy performance or behavior requirements
Custom layouts improve performance for complex UI arrangements and reduce overdraw when implemented correctly.
Overview: ViewGroup Lifecycle
A ViewGroup manages child views through two key steps:
- Measure pass — determines required sizes
- Layout pass — positions children
Both must be overridden carefully to ensure proper rendering.
Step 1 — Create a Custom Layout Class
Here’s a simple example of a custom ViewGroup that arranges children horizontally with padding.
Kotlin Implementation
class CustomHorizontalLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : ViewGroup(context, attrs, defStyle) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var totalWidth = 0
var maxHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChild(child, widthMeasureSpec, heightMeasureSpec)
totalWidth += child.measuredWidth
maxHeight = maxOf(maxHeight, child.measuredHeight)
}
val width = resolveSize(totalWidth, widthMeasureSpec)
val height = resolveSize(maxHeight, heightMeasureSpec)
setMeasuredDimension(width, height)
}
override fun onLayout(p0: Boolean, l: Int, t: Int, r: Int, b: Int) {
var currentX = paddingLeft
for (i in 0 until childCount) {
val child = getChildAt(i)
val childWidth = child.measuredWidth
val childHeight = child.measuredHeight
child.layout(
currentX,
paddingTop,
currentX + childWidth,
paddingTop + childHeight
)
currentX += childWidth
}
}
}
Java Implementation
public class CustomHorizontalLayout extends ViewGroup {
public CustomHorizontalLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int totalWidth = 0;
int maxHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
totalWidth += child.getMeasuredWidth();
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
}
int width = resolveSize(totalWidth, widthMeasureSpec);
int height = resolveSize(maxHeight, heightMeasureSpec);
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int currentX = getPaddingLeft();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
child.layout(currentX, getPaddingTop(),
currentX + childWidth, getPaddingTop() + childHeight);
currentX += childWidth;
}
}
}
Step 2 — Use Custom Layout in XML
Once implemented, you can reference the custom layout in XML:
<com.example.custom.CustomHorizontalLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text 2"/>
</com.example.custom.CustomHorizontalLayout>
This inflates your layout and arranges children horizontally at runtime.
Handling Padding & Margins
To honor child margins, override generateLayoutParams:
Kotlin
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context, attrs)
}
Java
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
Also override:
override fun generateDefaultLayoutParams() = MarginLayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT
)
This ensures layout respects margins.
Custom Attributes Support (Optional)
You can define custom attributes in res/values/attrs.xml:
<declare-styleable name="CustomHorizontalLayout">
<attr name="childSpacing" format="dimension"/>
</declare-styleable>
Read them in your constructor:
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomHorizontalLayout)
val spacing = typedArray.getDimensionPixelSize(
R.styleable.CustomHorizontalLayout_childSpacing, 0
)
typedArray.recycle()
Use this value when positioning children.
Common Mistakes & Fixes
Incorrect Measurements
Cause: Not measuring children correctly
Fix: Always call measureChild() before using measurement results.
Overlapping Children
Cause: Incorrect layout coordinates
Fix: Increment position offset appropriately in onLayout().
Ignoring Padding & Margins
Cause: Not handling margin params
Fix: Use MarginLayoutParams and adjust placement.
Performance Considerations
- Minimize layout passes (avoid unnecessary invalidation)
- Reduce nested hierarchies where possible
- Prefer
RecyclerView+ LayoutManager for large dynamic lists - Use
Viewcaching when child count is high
Extending ViewGroup gives you control, but careful performance tuning is essential.
Best Practices (2026 Updated)
- Use constraint-based layouts when default behaviors suffice
- Only create custom layouts when necessary
- Respect view lifecycle (measure → layout → draw)
- Handle configuration changes gracefully
- Profile with Layout Inspector in Android Studio


