In the last article we analyzed Android View measurements. DecorView root layout MeasureSpec = MeasureSpec = performMeasure() Find the core onMeasure() of the corresponding View by View#measure. If it is a ViewGroup, first recurse the child View, measure the parent View’s MeasureSpec and the child View’s LayoutParams as parameters, and then return layer by layer. Continuously save the measurement width and height of the ViewGroup.
Ok, after a short review, we return to the viewrotimpl #performTraverals method:
private void performTraversals() {
...
if (!mStopped) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
if(didLayout) { performLayout(lp, desiredWindowWidth, desiredWindowHeight); . }if(! cancelDraw && ! newSurface) {if(! skipDraw || mReportNextDraw) {if(mPendingTransitions ! =null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); }}... }Copy the code
Source code is very clear, continue our analysis of performLayout(). Let’s go!
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(TAG, "Laying out " + host + " to (" +
host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight()); . }finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}Copy the code
Host. layout() is assigned to a DecorView object by mView. The method takes parameters 0,0, host.getMeasuredWidth(), Host. getMeasuredHeight(), which represents the upper, right, and lower left positions of the View. DecorView is a FrameLayout subclass, and FrameLayout is a ViewGroup subclass. Host. layout calls ViewGroup#layout (ViewGroup#layout).
@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 {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true; }}Copy the code
The mTransition object is a LayoutTransition class, and the annotation says that it handles the animation effects of adding and removing subviews to a ViewGroup. Super.layout (L, t, r, b); super.layout(l, T, r); That is, the View#layout method is called and the top, right, and bottom four arguments are passed.
public class View implements...{... the publicvoid 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;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);// Sets the position relative to the parent layout
// Determine whether the position of the View has changed and whether a new layout is necessary
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
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); } } } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }}Copy the code
Measure () should be called again before layout() when the measurement method is skipped. Then isLayoutModeOptical(), where the comment is whether the layout of the ViewGroup is in the scope of the view. The implementation method in setOpticalFrame() does some judgment calculation and also calls setFrame(L, t, r, b).
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false; ...if(mLeft ! = left || mRight ! = right || mTop ! = top || mBottom ! = bottom) { changed =true;
// Remember our drawn bitint 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 positioninvalidate(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); ...return changed;
}Copy the code
MLeft, mTop, mRight, and mBottom are assigned to the left,top,right, and bottom positions relative to the parent layout. That means that the assignment now determines the position of the View itself in the parent layout. In addition, we will query the class method for getLeft() and other three points to see if they return values for those points with mLeft and other corresponding values, which we will talk about later.
After setFrame() we can finally see onLayout(). Click there to see the View#onLayout method:
public class View implements...{... protectedvoidOnLayout (Boolean changed, int left, int top, int right, int bottom) {} ···} public abstractclass ViewGroup extends View implements... @Override
protected abstract void onLayout(boolean changed.int l.int t.int r.int b); ...Copy the code
View#onLayout and ViewGroup#onLayout implement an empty method. But ViewGroup is an abstract method, which means that subclasses that inherit from ViewGroup must override onLayout(). In the last part, we analyzed that different viewgroups have different onmeasures (). Since the measurement is different, the layout method of onLayout() must be different. According to the logic in the last part, FrameLayout (DecorView) onLayout:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {
final int count = getChildCount();
ParentLeft depends on the padding and Foreground of the parent container
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);
/ / not to GONE
if(child.getVisibility() ! = GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams();// Get the measurement width and height of the child View
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;
// When the child View sets the horizontal layout_gravity property
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
// Middle calculation
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
// The right side of the calculation method
case Gravity.RIGHT:
if(! forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin;break;
}
// The left side of the calculation
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
// When the child View sets the vertical layout_gravity property
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;
}
// Layout the child elementschild.layout(childLeft, childTop, childLeft + width, childTop + height); }}}Copy the code
The FrameLayout#onLayout method calls the layoutChildren method directly. The FrameLayout#onLayout method is a bit long, but it’s easy to understand.
Let’s sort it out: The layout_gravity property of the child View, the LayoutParams property of the child View, and the Padding value of the parent layout are used to determine the upper left, lower right and lower left parameters of the child View. The child.layout method is then called to pass the layout flow from the parent container to the child elements.
View# Layout is an empty method that overwrites the method using a child View, such as TextView, CustomView, etc. Different views have different layouts. If you’re interested, you can see how they did it.
View# getWidth (), View# getMeasureWidth ()
MLeft, mRight, mTop, mBottom in View#setFrame() is a global variable. Assigned during the layout of the View.
View#getMeasureWidth() returns to our previous View measurement. The View#onMeasure method calls the View#setMeasuredDimension method. The View#setMeasuredDimensionRaw method is used to measure measureddimensionraw.
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}Copy the code
Simply assign mMeasuredWidth and mMeasuredHeight, so the value returned by View#getMeasureWidth is the measuredwidth value.
Their values are basically consistent, but when are they inconsistent? ChildView# layout is called. FrameLayout cannot be modified, but in our CustomView CustomView, OnLayout (childview.layout (0,0,100,100)); If View#getWidth() and View#getMeasureWidth() return inconsistent values, check for yourself.
So their values are the same when returned without special modifications, but their meanings are completely different, one in the measurement process and one in the layout process. I want you to pay a little attention.
The View layout process has been completely analyzed. Let’s summarize: Layout process is relatively simple, the last View measurement, we can get the width and height of the View, the layout of the ViewGroup layout, call the layout method, determine the location in the parent layout, in the onLayout() method to traverse the child View, Call the layout method of the child View and according to the size of the child View, View LayoutParams value, the parent View of the child View position limit as a parameter to complete the layout; And View measurement using the measured width and height to calculate the child View relative to the parent View position parameters, complete the layout. In the next part, we will cover the final step, drawing the View.