View workflow--layout process

Posted by sonnieboy on Mon, 07 Oct 2019 23:58:33 +0200

Calling process

The layout process starts with performTraversals(). After calling the measurement process, performTraversals() calls performLayout(), which is the starting point of the layout process.

ViewRootImpl # performLayout()

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;
        // host is DecorView
        final View host = mView;
        if (host == null) {
            return;
        }
        ...
        try {
            // The coordinates of the four vertices of the incoming window
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

            ...
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
    }

Because DecorView itself does not override layout(), nor does its parent FrameLayout, it calls layout() of ViewGroup.

ViewGroup # layout()

    @Override
    public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            // Processing animation
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            // Call layout() of View
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }

View # layout()

    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        // setFrame() sets the incoming window coordinates to global variables
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            // Distribute layout
            onLayout(changed, l, t, r, b);

            ...
        }
        ...
    }

Be careful!!! At this point, the onLayout() of DecorView is called, which in turn calls the onLayout() of the parent class.

DecorView # onLayout()

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    // ...
}

FrameLayout # onLayout()

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        // Start distributing the layout of the processing sub-View
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

FrameLayout # layoutChildren()

    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();

        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();

        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;

                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }

                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
                // Processing variables horizontally and vertically
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }

                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }
                // Traversing through the sub-View, calling the layout() of the sub-View
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

Approaches to View and ViewGroup

View # layout()

    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
        // mXXX is a global variable (the relative coordinates of this View relative to the parent View) that is saved first
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        // setFrame() assigns the incoming window coordinates to the global variable mXXX
        // When four vertices are determined, the position of the View is determined.
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            
            onLayout(changed, l, t, r, b);

            ...
        }
        ...
    }

View # setFrame()

    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (DBG) {
            Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }

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

            // Remember our drawn bit
            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 our old position
            invalidate(sizeChanged);
            // Reassign
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            mPrivateFlags |= PFLAG_HAS_BOUNDS;
            ...
        }
        return changed;
    }

View # onLayout()

Without a unified implementation, the specific implementation is related to the specific layout.

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

ViewGroup # layout()

    @Override
    public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            // Processing animation
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            // Call layout() of View
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }

ViewGroup # onLayout()

ViewGroup is an abstract class, and classes that inherit it all implement onLayout()

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

Measuring Width and Actual Width and Height

Acquisition of measurement width and height

width = view.getMeasuredWidth();
height = view.getMeasuredHeight();

public final int getMeasuredWidth() {
    return mMeasuredWidth & MEASURED_SIZE_MASK;
}

Acquire actual width and height

width = view.getWidth();
height = view.getHeight();

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

Difference

The four values of mLeft, mRight, mTop and mBottom are derived from the layout() parameters, and most of the parameters passed are determined by getMeasureWidth() and getMeasureHeight(), so in the default implementation, the measurement width and final width of View are the same, that is, mRight - mLeft == mMeasureWidth & MEASURED_SIZE_MASK.
But the timing of assignment is different, so the measurement should be earlier. Some special cases may lead to inconsistencies between the two, such as rewriting the layout method of View or measuring width and height from previous measurements.