View and ViewGroup layout process
The process here is a little simpler than measure. Let's start with the layout() of View Group. The layout() of View Group is mainly called after judging some conditions.
View.java
public void layout(int l, int t, int r, int b) {
if (DBG_SYSTRACE_LAYOUT) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout : " + getClass().getSimpleName());
}
// != 0 means setting this flag when measuring, that is, measuring according to measure cache, instead of calling onMeasure() directly, so we need to restart onMeasure once.
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
/// M: Monitor onLayout time if longer than 3s print log.
if (DBG_LAYOUT || DBG_MEASURE_LAYOUT) {
Xlog.d(VIEW_LOG_TAG, "view onMeasure start (measure cache), this =" + this
+ ", widthMeasureSpec = " + MeasureSpec.toString(mOldWidthMeasureSpec)
+ ", heightMeasureSpec = " + MeasureSpec.toString(mOldHeightMeasureSpec));
}
long logTime = System.currentTimeMillis();
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
long nowTime = System.currentTimeMillis();
if (nowTime - logTime > DBG_TIMEOUT_VALUE) {
Xlog.d(VIEW_LOG_TAG, "[ANR Warning]onMeasure time too long, this =" + this
+ "time =" + (nowTime - logTime) + " ms");
}
if (DBG_LAYOUT || DBG_MEASURE_LAYOUT) {
Xlog.d(VIEW_LOG_TAG, "view onMeasure end (measure cache), this =" + this
+ ", mMeasuredWidth = " + mMeasuredWidth + ", mMeasuredHeight = "
+ mMeasuredHeight + ", time =" + (nowTime - logTime) + " ms");
}
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;//Recovery of markers
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//Visual/optical boundary layout, generally false
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (DBG_LAYOUT || DBG_MEASURE_LAYOUT) {
Xlog.d(VIEW_LOG_TAG, "view layout start, this = " + this + ", mLeft = " + mLeft
+ ", mTop = " + mTop + ", mRight = " + mRight + ", mBottom = " + mBottom
+ ", changed = " + changed);
}
//PFLAG_LAYOUT_REQUIRED is also set at measure time.
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
/// M: Monitor onLayout time if longer than 3s print log.
long logTime = System.currentTimeMillis();
onLayout(changed, l, t, r, b);//This method view and ViewGroup are not implemented, but there are subclasses to implement it.
long nowTime = System.currentTimeMillis();
if (nowTime - logTime > DBG_TIMEOUT_VALUE) {
Xlog.d(VIEW_LOG_TAG, "[ANR Warning]onLayout time too long, this =" + this
+ "time =" + (nowTime - logTime) + " ms");
}
if (DBG_LAYOUT || DBG_MEASURE_LAYOUT) {
Xlog.d(VIEW_LOG_TAG, "view layout end, this =" + this + ", mLeft = " + mLeft
+ ", mTop = " + mTop + ", mRight = " + mRight + ", mBottom = " + mBottom
+ ", time =" + (nowTime - logTime) + " ms");
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;//Recovery of markers
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);//Interface callback
}
}
} else {
if (DBG_LAYOUT || DBG_MEASURE_LAYOUT) {
Xlog.d(VIEW_LOG_TAG, "view layout end 2 (use previous layout), this = " + this
+ ", mLeft = " + mLeft + ", mTop = " + mTop
+ ", mRight = " + mRight + ", mBottom = " + mBottom);
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if (DBG_SYSTRACE_LAYOUT) {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
As can be seen from the above code, the layout process first decides whether it needs to measure again based on whether the PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT variable has been set in the measurement process before, and then calls the setFrame to determine the location of its four points, which are left, top. right and bottom. With these four points, the subsequent drawing can be accurate.
Then the onLayout process is executed according to the PFLAG_LAYOUT_REQUIRED variable set in measure. In fact, neither View nor ViewGroup implements the onLayout process. The real implementation is in each subclass, which will be analyzed later. After that, if the view has set the OnLayoutChangeListener interface, the onLayoutChange() method of these interfaces will be called back.
For the time being, the setFrame() process is not analyzed here, and we will see it later in draw().
As mentioned above, View and ViewGroup do not implement onLayout() by themselves, but need subclasses to implement it. Here, we will take LinearLayout as an example to analyze the implementation process.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
Here we only analyze the vertical layout.
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
As you can see from the above code, the first child is determined by the Gravity attribute first.
The top position is then looped to get child Top and child Left, while the width is obtained to call setChildFrame(). The setChildFrame() simply calls the child.
The layout process of each child is called recursively.