English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Preface
In the previous article, the author detailed the first of the View's three major workflows, the Measure process. If readers are not familiar with the measurement process, they can refer to the previous article. The measurement process mainly measures the View tree to obtain the measured width and height of each View. So, with the measured width and height, it is time to proceed with the layout process, which is much simpler than the measurement process. Let's start with a detailed analysis of the layout process.
ViewGroup's layout process
The previous article mentioned that the three major processes start from the ViewRootImpl#performTraversals method. Inside this method, the measure, layout, and draw processes are performed by calling performMeasure, performLayout, and performDraw methods, respectively. So, let's start with the performLayout method. Let's first look at its source code:
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()); // 1 //Omitted... } Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; }
From the above code, it can be seen that the host.layout method marked with ① is called directly, where host is DecorView. For DecorView, calling the layout method means performing layout on itself. Noticing that the parameters passed are 0, 0, host.getMeasuredWidth, host.getMeasuredHeight, which represent the top, bottom, left, and right positions of a View, respectively. It is obvious that the left top position of DecorView is 0, and the width and height are its measured width and height. Since the View's layout method is of final type, subclasses cannot override it, so we can directly look at the View#layout method:
public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG;3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags;3 &= ~PFLAG;3_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); // 1 if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); // 2 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; mPrivateFlags;3 |= PFLAG;3_IS_LAID_OUT; }
Firstly, let's look at the code marked with ①, which calls the setFrame method and passes in the four position information. This method is used to determine the positions of the four vertices of the View, i.e., to initialize the four values of mLeft, mRight, mTop, and mBottom. Once initialized, the layout process of ViewGroup is also completed.
Then, let's first look at the View#setFrame method:
protected boolean setFrame(int left, int top, int right, int bottom) { //Omitted... mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); //Omitted... return changed; }
It can be seen that it initializes the four values of mLeft, mTop, mRight, and mBottom. For each View, including ViewGroup, these four values store the position information of the View. Therefore, these four values are the final width and height, which means that if you want to get the position information of the View, you should call the getLeft(), getTop() and other methods after the layout method is completed to obtain the final width and height. If you call the corresponding methods before this, you can only get the result of 0, so we usually obtain the width and height information of the View in the onLayout method.
After the position parameters of ViewGroup itself are set, we see that the method number ② is called next, that is, the onLayout() method, which is called in ViewGroup to determine the position of the child View. That is, within this method, the child View will call its own layout method to further complete its layout process. Since the onMeasure method of different layout containers has different implementations, it is impossible to say once for all layout methods. Additionally, the previous article explained FrameLayout#onMeasure, so now let's also explain FrameLayout#onLayout method:}}
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { //Pass the position parameters of the parent container into it 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(); //The following four values will affect the layout parameters of the child View //The padding of the parent container and Foreground determine parentLeft final int parentLeft = getPaddingLeftWithForeground(); //The width of the parent container, padding, and Foreground determine parentRight 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(); //Get the measured 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 has set the horizontal direction layout_gravity attribute, different childLeft values are set according to different attribute settings //childLeft represents the X coordinate of the upper left corner of the child View switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { /* Horizontal centering, since the child View needs to be displayed in the horizontal center position, therefore, it is necessary to calculate the following first: * (parentRight - parentLeft -width)/2 At this time, the result is the parent container minus the width of the child View * half of the remaining space, then after adding parentLeft, it is the horizontal coordinate of the upper left corner of the child View (at this moment, it is exactly in the middle position), * If the child View is still constrained by margin, since leftMargin makes the child View shift to the right and rightMargin makes it shift to the left, so finally * is +leftMargin -rightMargin . */ case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; //The horizontal alignment is right, and the horizontal coordinate of the upper left corner of the child View equals parentRight minus the measured width of the child View minus the margin case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } //If the horizontal direction of the layout_gravity is not set, it is default centered on the left //Horizontally centered on the left, the horizontal coordinate of the upper left corner of the child View is equal to parentLeft plus the margin value of the child View case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } //When the child View sets the layout_gravity in the vertical direction, the childTop is set according to different properties //childTop represents the Y value of the upper left corner coordinate of the child View //The analysis method is the same as above 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 element, with the upper left corner coordinates as (childLeft, childTop), and the lower right corner coordinates as (childLeft+width,childTop+height) child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }
From the source code, it can be seen that the onLayout method internally calls the layoutChildren method, and layoutChildren is the specific implementation.
Let's sort out the above logic first: first, get the padding value of the parent container, then iterate through each child View, determine the layout parameters of the child View according to the layout_gravity attribute of the child View, the measured width and height of the child View, and the padding value of the parent container, and then call the child.layout method to pass the layout process from the parent container to the child element.
Then, the layout process of ViewGroup has been analyzed, so let's continue to analyze the layout process of child elements.
The layout process of child View is also
The layout process of child View is also very simple. If the child View is a ViewGroup, it will repeat the above steps. If it is a View, it will directly call the View#layout method, and set the four layout parameters of the view within the method. Then call the onLayout method, let's see the View#onLayout method:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
This is an empty implementation, mainly used to override this method in our custom View to implement custom layout logic.
So far, the layout process of View has been fully analyzed. It can be seen that the logic of the layout process is much simpler than that of the measurement process. It is relatively complex to obtain the measurement width and height of a View, while the layout process is to determine the four position parameters of a View based on the obtained measurement width and height. In the next article, we will talk about the last process: the drawing process. I hope this article will help everyone understand the working process of View, thank you for reading.
More Reading
Complete Analysis of Android View Measurement Process (Measure)
Complete Analysis of Android View Drawing Process (Draw)
That's all for this article. I hope it will be helpful to everyone's learning, and I also hope everyone will support the Yelling Tutorial more.
Statement: The content of this article is from the Internet, and the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously. This website does not own the copyright, has not been manually edited, and does not assume any relevant legal liability. If you find any content suspected of copyright infringement, please send an email to notice#w3Please send an email to codebox.com (replace # with @ when sending emails) to report any violations, and provide relevant evidence. Once verified, this site will immediately delete the infringing content.