[[424470]] Preface In the previous article, we explained the Measure process of View. Today, let’s talk about Layout. The layout method of View is used to determine the position of View. The layout method of ViewGroup not only determines its own position, but also determines the position of its child Views. Android advanced in-depth understanding of View measurement process mechanism 1. Detailed explanation of Layout process source code 1. performLayout The three major workflows of View start from ViewRootImpl#performTraversals, where performMeasure, performLayout, and performDraw methods correspond to the measurement, layout, and drawing of View respectively; Analyze the View layout process starting from performLayout; - private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
- int desiredWindowHeight) {
- mLayoutRequested = false ;
- mScrollMayChange = true ;
- mInLayout = true ;
- final View host = mView;
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout" );
- try {
- host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
- //Omitted...
- finally
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
- mInLayout = false ;
- }
The mView in the method is actually DecorView, so host also represents DecorView. DecorView is actually a FrameLayout. ViewGroup does not override the layout method, so let's take a look at the View#layout method. 2. Layout - /**
- * Source code analysis starting point: layout()
- * Function: Determine the position of the View itself, that is, set the positions of the four vertices of the View itself
- */
- public void layout( int l, int t, int r, int b) {
- // The four vertices of the current view
- int oldL = mLeft;
- int oldT = mTop;
- int oldB = mBottom;
- int oldR = mRight;
- // 1. Determine the position of the View : setFrame() / setOpticalFrame()
- // Initialize the values of the four vertices, determine whether the current View size and position have changed & return
- // setFrame() -> Analysis 1
- // setOpticalFrame() ->Analysis 2
- boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
- // 2. If the view size & position changes
- // Will reposition all child Views of this View in the parent container: onLayout()
- if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
- onLayout(changed, l, t, r, b);
- // For the layout process of a single View : Since a single View has no child Views , onLayout() is an empty implementation -> Analysis 3
- // For the layout process of ViewGroup: Since the position determination is related to the specific layout, onLayout() is an abstract method in ViewGroup and needs to be rewritten by a custom method (the following chapter will explain in detail)
- }
- /**
- * Analysis 1: setFrame()
- * Function: Set the four vertex positions of the View itself according to the four position values passed in
- * That is: finally determine the position of the View itself
- */
- protected boolean setFrame( int left , int top , int right , int bottom) {
- // The following assignment statement records the position information of the view, that is, determines the four vertices of the View
- // This determines the position of the view
- mLeft = left ;
- mTop = top ;
- mRight = right ;
- mBottom = bottom;
- mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
- }
- /**
- * Analysis 2: setOpticalFrame()
- * Function: Set the four vertex positions of the View itself according to the four position values passed in
- * That is: finally determine the position of the View itself
- */
- private boolean setOpticalFrame( int left , int top , int right , int bottom) {
- Insets parentInsets = mParent instanceof View ?
- (( View ) mParent).getOpticalInsets() : Insets.NONE;
- Insets childInsets = getOpticalInsets();
- // Internally, it actually calls setFrame()
- return setFrame(
- left + parentInsets.left - childInsets.left ,
- top + parentInsets. top - childInsets. top ,
- right + parentInsets. left + childInsets. right ,
- bottom + parentInsets.top + childInsets.bottom);
- }
- // Return to the original call
- /**
- * Analysis 3: onLayout()
- * Note: For the layout process of a single View
- * 1. Since a single View has no child Views , onLayout() is an empty implementation
- * 2. Since the position of the View itself has been calculated in layout(): setFrame() / setOpticalFrame()
- * 3. So the layout process of a single View is completed after layout()
- */
- protected void onLayout(boolean changed, int left , int top , int right , int bottom) {
- // Parameter description
- // changed The size and position of the current View have changed
- // left left position
- // top position
- // right right position
- // bottom bottom position
- }
3. setFrame The layout method is used to determine its own position. It calls the setOpticalFrame, setFrame and onLayout methods internally. The setOpticalFrame calls the setFrame method internally. So let's first look at the setFrame method, as follows - protected boolean setFrame( int left , int top , int right , int bottom) {
- boolean changed = false ;
- if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
- //Determine whether the position of the View has changed
- changed = true ;
- //Remember our drawn bit
- int drawn = mPrivateFlags & PFLAG_DRAWN;
- int oldWidth = mRight - mLeft; //Get the original width
- int oldHeight = mBottom - mTop; //Get the original height
- int newWidth = right - left ; //Get the new width
- int newHeight = bottom - top ; //Get the new height
- // Determine whether the size of the View has changed
- boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
- // Invalidate our old position
- invalidate(sizeChanged);
- //Initialize mLeft, mTop, mRight, and mBottom, and the position of View itself is determined.
- mLeft = left ;
- mTop = top ;
- mRight = right ;
- mBottom = bottom;
- mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
- mPrivateFlags |= PFLAG_HAS_BOUNDS;
- //If the View size changes, the View #sizeChange method will be executed, and the View #onSizeChanged method will be called inside the sizeChange method .
- if (sizeChanged) {
- sizeChange(newWidth, newHeight, oldWidth, oldHeight);
- }
- //Omitted...
- }
- return changed;
- }
In the setFrame method, mLeft, mTop, mRight, and mBottom are initialized. mLeft and mTop correspond to the horizontal and vertical coordinates of the upper left corner of the View, respectively. mRight and mBottom correspond to the horizontal and vertical coordinates of the lower right corner of the View, respectively. Once the coordinates of the four vertices of the View are determined, the position of the View itself is also determined. 4. FrameLayout#onLayout Back to the layout method, after determining its own position through the setFrame method, the onLayout method will be called next. This method is actually used to determine the position of the child View; However, neither View nor ViewGroup actually implements onLayout, because onLayout is similar to onMeasure, and its process is related to the specific layout; Take FrameLayout as an example to analyze the onLayout process, FrameLayout#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 */);
- }
- The layoutChildren method is called internally
- void layoutChildren( int left , int top , int right , int bottom,
- boolean forceLeftGravity) {
- final int count = getChildCount(); //Get the number of child Views
- //parentLeft and parentTop represent the horizontal and vertical coordinates of the upper left corner of the area occupied by the child View
- //parentRight and parentBottom represent the horizontal and vertical coordinates of the lower right corner of the area occupied by the child View respectively
- final int parentLeft = getPaddingLeftWithForeground();
- final int parentRight = right - left - getPaddingRightWithForeground();
- final int parentTop = getPaddingTopWithForeground();
- final int parentBottom = bottom - top - getPaddingBottomWithForeground();
- mForegroundBoundsChanged = true ;
- //Traverse the sub- View
- for ( int i = 0; i < count ; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- //Get the measured width and height of the child View
- final int width = child.getMeasuredWidth();
- final int height = child.getMeasuredHeight();
- int childLeft;
- int childTop;
- //Get the Gravity set for the child View . If the child View does not have a Gravity set, use the default Gravity: DEFAULT_CHILD_GRAVITY.
- 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;
- // In the horizontal direction, determine childLeft by setting Gravity, that is, the horizontal coordinate of the upper left corner of each child View
- 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;
- }
- //In the vertical direction, determine the childTop by setting the Gravity, that is, the ordinate of the upper left corner of each child View
- 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;
- }
- //Call the layout method of the child View
- child.layout(childLeft, childTop, childLeft + width, childTop + height);
- }
- }
- }
In the process of traversing all child Views within this method, the Gravity set by the child View is used to obtain the horizontal and vertical coordinates of the upper left corner of the child View, namely childLeft and childTop, and finally the layout method of the child View is executed to determine the position of the child View. 5. LinearLayout#onLayout Analysis of onLayout() overridden by LinearLayout - /**
- * Source code analysis: onLayout() overridden by LinearLayout
- * Note: The overwritten logic is similar to onMeasure() in LinearLayout measure process
- */
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- // Choose different processing methods according to their own direction attributes
- if (mOrientation == VERTICAL) {
- layoutVertical(l, t, r, b);
- } else {
- layoutHorizontal(l, t, r, b);
- }
- }
- // Since the vertical and horizontal directions are similar, only the vertical processing process is analyzed here -> Analysis 1
- /**
- * Analysis 1: layoutVertical(l, t, r, b)
- */
- void layoutVertical( int left , int top , int right , int bottom) {
- // Number of sub- Views
- final int count = getVirtualChildCount();
- // 1. Traverse the sub -View
- for ( int i = 0; i < count ; i++) {
- final View child = getVirtualChildAt(i);
- if (child == null ) {
- childTop += measureNullChild(i);
- } else if (child.getVisibility() != GONE) {
- // 2. Calculate the measured width/height of the child View
- final int childWidth = child.getMeasuredWidth();
- final int childHeight = child.getMeasuredHeight();
- // 3. Determine the position of its own child View
- // That is: recursively calling the child View 's setChildFrame() actually calls the child View 's layout() -> Analysis 2
- setChildFrame(child, childLeft, childTop + getLocationOffset(child),
- childWidth, childHeight);
- // childTop gradually increases, that is, the following child elements will be placed at the bottom
- // This conforms to the characteristics of the vertical LinearLayout
- childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
- i += getChildrenSkipCount(child, i);
- }
- }
- }
- /**
- * Analysis 2: setChildFrame()
- */
- private void setChildFrame( View child, int left , int top , int width, int height){
- child.layout( left , top , left ++ width, top + height);
- // setChildFrame() just calls layout() of the child View
- // In the layout() of the child View , call setFrame() to determine the four vertices of the View
- // That is, determine the position of the child View
- // This loop continues to determine the positions of all child Views , and finally determines the position of the ViewGroup
- }
Summarize The core of View's layout process is to override ViewGroup's onLayout method. Its process is to get the width and height of the child View, and then implement its own logic for laying out the child View. It is generally used in conjunction with the onMeasure method. |