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.