Visit Sponsor

Written by 11:47 am Android

Android Custom Layout Example by Extending ViewGroup

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:

  1. Measure pass — determines required sizes
  2. 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 View caching 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
Visited 8 times, 1 visit(s) today
Close