Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding View Layout Process: How ViewGroup Differs from View

Tech 1

Core Layout Mechanism in Views

The layout() method is responsible for positioning a View on the screen by establishing its boundaries through four critical parameters: mLeft, mTop, mBottom, and mRight. These values define the rectangular area occupied by the View.

When examining the internal implementation:

if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
    changed = true;

    int drawn = mPrivateFlags & PFLAG_DRAWN;

    int oldWidth = mRight - mLeft;
    int oldHeight = mBottom - mTop;
    int newWidth = right - left;
    int newHeight = bottom - top;
    boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

    invalidate(sizeChanged);

    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
    mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}
return changed;

The method compares incoming coordinates with existing ones. If any boundary differs, the View is marked as changed. The system then records the old dimensions for later comparison, calculates new dimensions, and triggers invalidation if sizing occurred. Finally, all four boundary values are updated along with the render node.

The layout() method essentially performs one operation: it accepts coordinate parameters and assigns them to internal fields. This simplicity enables efficient dimension retrieval:

public final int getWidth() {  
    return mRight - mLeft;  
}

ViewGroup Layout Behavior

When inspecting ViewGroup's layout implementation:

@Override  
public final void layout(int l, int t, int r, int b) {  
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        super.layout(l, t, r, b);
    } else {
        mLayoutCalledWhileSuppressed = true;
    }
}

Despite the additional checks, ViewGroup ultimately delegates to View's layout() method. The key distinction lies in the onLayout() callback, which is empty in View but abstract in ViewGroup:

@Override  
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

Every ViewGroup subclass must implement onLayout() to position its children. The implementation typically iterates through child views, calling layout() on each to establish their positions.

Building a Custom VerticalLayout

Creating a custom ViewGroup resembling LinearLayout's vertical orientation involves several key steps.

Extend ViewGroup

class VerticalLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr)

Override generateLayoutParams

override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams? {  
    return MarginLayoutParams(context, attrs)  
}

This enables margin support for child views.

Implement onMeasure

The measurement phase processes widthMeasureSpec and heightMeasureSpec to determine appropriate dimensions based on parent constraints.

When MeasureSpec.EXACTLY is specified, use the provided size directly. For AT_MOST or UNSPECIFIED modes, calculate dimensions by summing child contributions:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)

    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val heightMode = MeasureSpec.getMode(heightMeasureSpec)
    val providedWidth = MeasureSpec.getSize(widthMeasureSpec)
    val providedHeight = MeasureSpec.getSize(heightMeasureSpec)

    var totalHeight = 0
    var maxWidth = 0

    for (i in 0 until childCount) {
        val child = getChildAt(i)
        measureChild(child, widthMeasureSpec, heightMeasureSpec)

        val params = child.layoutParams as MarginLayoutParams
        val childWidth = child.measuredWidth + params.leftMargin + params.rightMargin
        val childHeight = child.measuredHeight + params.topMargin + params.bottomMargin

        maxWidth = maxOf(maxWidth, childWidth)
        totalHeight += childHeight
    }

    setMeasuredDimension(
        if (widthMode == MeasureSpec.EXACTLY) providedWidth else maxWidth,
        if (heightMode == MeasureSpec.EXACTLY) providedHeight else totalHeight
    )
}

The ViewGroup's final width equals the widest child (including margins), while height equals the cumulative height of all children.

Implement onLayout

Positioning children requires calculating the top coordinate for each view, incrementing by the previous child's bottom edge:

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    var currentTop = 0

    for (i in 0 until childCount) {
        val child = getChildAt(i)
        val params = child.layoutParams as MarginLayoutParams

        currentTop += params.topMargin

        val left = params.leftMargin
        val top = currentTop
        val right = left + child.measuredWidth
        val bottom = top + child.measuredHeight

        child.layout(left, top, right, bottom)

        currentTop += child.measuredHeight + params.bottomMargin
    }
}

Each child receives fixed left values (from margin) and dynamically calculated top values. The right and bottom derive from width and height respectively.

Usage Example

<com.example.VerticalLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/primary"
        android:text="Header" />

    <TextView
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:layout_marginTop="20dp"
        android:background="@color/secondary"
        android:text="Content" />

    <TextView
        android:layout_width="140dp"
        android:layout_height="100dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:background="@color/accent"
        android:text="Footer" />

</com.example.VerticalLayout>

The layout result displays children stacked vertically, mainatining proper spacing through margin values.

Summary

The layout phase establishes where each View appears on screen. View handles its own positioning, while ViewGroup additionally manages child positioning through onLayout(). The process involves comparing incoming coordinates, updating boundary values, and invalidating regions when dimensions change.

Related Articles

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.