Android advanced in-depth understanding of the View layout process principle

Android advanced in-depth understanding of the View layout process principle

[[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;

  1. private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
  2. int desiredWindowHeight) {
  3. mLayoutRequested = false ;
  4. mScrollMayChange = true ;
  5. mInLayout = true ;
  6. final View host = mView;
  7. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout" );
  8. try {
  9. host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
  10. //Omitted...
  11. finally
  12. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  13. }
  14. mInLayout = false ;
  15. }

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

  1. /**
  2. * Source code analysis starting point: layout()
  3. * Function: Determine the position of the View itself, that is, set the positions of the four vertices of the View itself
  4. */
  5. public void layout( int l, int t, int r, int b) {
  6. // The four vertices of the current view
  7. int oldL = mLeft;
  8. int oldT = mTop;
  9. int oldB = mBottom;
  10. int oldR = mRight;
  11. // 1. Determine the position of the View : setFrame() / setOpticalFrame()
  12. // Initialize the values ​​of the four vertices, determine whether the current View size and position have changed & return
  13. // setFrame() -> Analysis 1
  14. // setOpticalFrame() ->Analysis 2
  15. boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
  16. // 2. If the view size & position changes
  17. // Will reposition all child Views of this View in the parent container: onLayout()
  18. if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
  19. onLayout(changed, l, t, r, b);
  20. // For the layout process of a single View : Since a single View has no child Views , onLayout() is an empty implementation -> Analysis 3
  21. // 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)
  22. }
  23. /**
  24. * Analysis 1: setFrame()
  25. * Function: Set the four vertex positions of the View itself according to the four position values ​​passed in
  26. * That is: finally determine the position of the View itself
  27. */
  28. protected boolean setFrame( int   left , int   top , int   right , int bottom) {
  29. // The following assignment statement records the position information of the view, that is, determines the four vertices of the View
  30. // This determines the position of the view
  31. mLeft = left ;
  32. mTop = top ;
  33. mRight = right ;
  34. mBottom = bottom;
  35. mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
  36. }
  37. /**
  38. * Analysis 2: setOpticalFrame()
  39. * Function: Set the four vertex positions of the View itself according to the four position values ​​passed in
  40. * That is: finally determine the position of the View itself
  41. */
  42. private boolean setOpticalFrame( int   left , int   top , int   right , int bottom) {
  43. Insets parentInsets = mParent instanceof View ?
  44. (( View ) mParent).getOpticalInsets() : Insets.NONE;
  45. Insets childInsets = getOpticalInsets();
  46. // Internally, it actually calls setFrame()
  47. return setFrame(
  48. left + parentInsets.left - childInsets.left ,
  49. top + parentInsets. top - childInsets. top ,
  50. right + parentInsets. left + childInsets. right ,
  51. bottom + parentInsets.top + childInsets.bottom);
  52. }
  53. // Return to the original call
  54. /**
  55. * Analysis 3: onLayout()
  56. * Note: For the layout process of a single View
  57. * 1. Since a single View has no child Views , onLayout() is an empty implementation
  58. * 2. Since the position of the View itself has been calculated in layout(): setFrame() / setOpticalFrame()
  59. * 3. So the layout process of a single View is completed after layout()
  60. */
  61. protected void onLayout(boolean changed, int   left , int   top , int   right , int bottom) {
  62. // Parameter description
  63. // changed The size and position of the current View have changed
  64. // left left position
  65. // top position
  66. // right right position
  67. // bottom bottom position
  68. }

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

  1. protected boolean setFrame( int   left , int   top , int   right , int bottom) {
  2. boolean changed = false ;
  3. if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
  4. //Determine whether the position of the View has changed
  5. changed = true ;
  6. //Remember our drawn bit  
  7. int drawn = mPrivateFlags & PFLAG_DRAWN;
  8. int oldWidth = mRight - mLeft; //Get the original width
  9. int oldHeight = mBottom - mTop; //Get the original height
  10. int newWidth = right - left ; //Get the new width
  11. int newHeight = bottom - top ; //Get the new height
  12. // Determine whether the size of the View has changed
  13. boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
  14. // Invalidate our old position
  15. invalidate(sizeChanged);
  16. //Initialize mLeft, mTop, mRight, and mBottom, and the position of View itself is determined.
  17. mLeft = left ;
  18. mTop = top ;
  19. mRight = right ;
  20. mBottom = bottom;
  21. mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
  22. mPrivateFlags |= PFLAG_HAS_BOUNDS;
  23. //If the View size changes, the View #sizeChange method will be executed, and the View #onSizeChanged method will be called inside the sizeChange method .
  24. if (sizeChanged) {
  25. sizeChange(newWidth, newHeight, oldWidth, oldHeight);
  26. }
  27. //Omitted...
  28. }
  29. return changed;
  30. }

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

  1. @Override
  2. protected void onLayout(boolean changed, int   left , int   top , int   right , int bottom) {
  3. layoutChildren( left , top , right , bottom, false /* no   force   left gravity */);
  4. }
  5. The layoutChildren method is called internally
  6. void layoutChildren( int   left , int   top , int   right , int bottom,
  7. boolean forceLeftGravity) {
  8. final int   count = getChildCount(); //Get the number of child Views
  9. //parentLeft and parentTop represent the horizontal and vertical coordinates of the upper left corner of the area occupied by the child View
  10. //parentRight and parentBottom represent the horizontal and vertical coordinates of the lower right corner of the area occupied by the child View respectively
  11. final int parentLeft = getPaddingLeftWithForeground();
  12. final int parentRight = right - left - getPaddingRightWithForeground();
  13. final int parentTop = getPaddingTopWithForeground();
  14. final int parentBottom = bottom - top - getPaddingBottomWithForeground();
  15. mForegroundBoundsChanged = true ;
  16. //Traverse the sub- View  
  17. for ( int i = 0; i < count ; i++) {
  18. final View child = getChildAt(i);
  19. if (child.getVisibility() != GONE) {
  20. final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  21. //Get the measured width and height of the child View
  22. final int width = child.getMeasuredWidth();
  23. final int height = child.getMeasuredHeight();
  24. int childLeft;
  25. int childTop;
  26. //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.
  27. int gravity = lp.gravity;
  28. if (gravity == -1) {
  29. gravity = DEFAULT_CHILD_GRAVITY;
  30. }
  31. final int layoutDirection = getLayoutDirection();
  32. final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
  33. final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
  34. // In the horizontal direction, determine childLeft by setting Gravity, that is, the horizontal coordinate of the upper left corner of each child View
  35. switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
  36. case Gravity.CENTER_HORIZONTAL:
  37. childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
  38. lp.leftMargin - lp.rightMargin;
  39. break;
  40. case Gravity. RIGHT :
  41. if (!forceLeftGravity) {
  42. childLeft = parentRight - width - lp.rightMargin;
  43. break;
  44. }
  45. case Gravity. LEFT :
  46. default :
  47. childLeft = parentLeft + lp.leftMargin;
  48. }
  49. //In the vertical direction, determine the childTop by setting the Gravity, that is, the ordinate of the upper left corner of each child View
  50. switch (verticalGravity) {
  51. case Gravity. TOP :
  52. childTop = parentTop + lp.topMargin;
  53. break;
  54. case Gravity.CENTER_VERTICAL:
  55. childTop = parentTop + (parentBottom - parentTop - height) / 2 +
  56. lp.topMargin - lp.bottomMargin;
  57. break;
  58. case Gravity.BOTTOM:
  59. childTop = parentBottom - height - lp.bottomMargin;
  60. break;
  61. default :
  62. childTop = parentTop + lp.topMargin;
  63. }
  64. //Call the layout method of the child View
  65. child.layout(childLeft, childTop, childLeft + width, childTop + height);
  66. }
  67. }
  68. }

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

  1. /**
  2. * Source code analysis: onLayout() overridden by LinearLayout
  3. * Note: The overwritten logic is similar to onMeasure() in LinearLayout measure process
  4. */
  5. @Override
  6. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  7. // Choose different processing methods according to their own direction attributes
  8. if (mOrientation == VERTICAL) {
  9. layoutVertical(l, t, r, b);
  10. } else {
  11. layoutHorizontal(l, t, r, b);
  12. }
  13. }
  14. // Since the vertical and horizontal directions are similar, only the vertical processing process is analyzed here -> Analysis 1
  15. /**
  16. * Analysis 1: layoutVertical(l, t, r, b)
  17. */
  18. void layoutVertical( int   left , int   top , int   right , int bottom) {
  19. // Number of sub- Views
  20. final int   count = getVirtualChildCount();
  21. // 1. Traverse the sub -View  
  22. for ( int i = 0; i < count ; i++) {
  23. final View child = getVirtualChildAt(i);
  24. if (child == null ) {
  25. childTop += measureNullChild(i);
  26. } else if (child.getVisibility() != GONE) {
  27. // 2. Calculate the measured width/height of the child View
  28. final int childWidth = child.getMeasuredWidth();
  29. final int childHeight = child.getMeasuredHeight();
  30. // 3. Determine the position of its own child View
  31. // That is: recursively calling the child View 's setChildFrame() actually calls the child View 's layout() -> Analysis 2
  32. setChildFrame(child, childLeft, childTop + getLocationOffset(child),
  33. childWidth, childHeight);
  34. // childTop gradually increases, that is, the following child elements will be placed at the bottom
  35. // This conforms to the characteristics of the vertical LinearLayout
  36. childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
  37. i += getChildrenSkipCount(child, i);
  38. }
  39. }
  40. }
  41. /**
  42. * Analysis 2: setChildFrame()
  43. */
  44. private void setChildFrame( View child, int   left , int   top , int width, int height){
  45. child.layout( left , top , left ++ width, top + height);
  46. // setChildFrame() just calls layout() of the child View
  47. // In the layout() of the child View , call setFrame() to determine the four vertices of the View
  48. // That is, determine the position of the child View
  49. // This loop continues to determine the positions of all child Views , and finally determines the position of the ViewGroup
  50. }

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.

<<:  WeChat launches "One ID Check": quickly check how many phone numbers you have

>>:  A must-read for designers! Product Data Planning Guide

Recommend

Bird population census: AI has started to check the bird population!

According to statistics from the World Wildlife F...

Brand marketing strategy, understand these 4 points!

Go-to -Market Strategy refers to the marketing st...

Learn how to use the AARRR model to achieve user growth in one article

" User growth is essentially a precise, low-...

How much does it cost to be a Yunfu agent for a printing app?

How much does it cost to be an agent of Yunfu Pri...

Tencent's operational path over the years

I would like to share with you four short stories...

There is a Longquanyi in Chengdu

When it comes to sightseeing in Chengdu, Longquan...

Hao Yan: What are the reasons that lead to over-optimization of websites?

In search engine optimization, the last thing man...