Android View Drawing Process II: layout Layout
The layout(int left,int top,int right,int bottom) method is the second step of the View mapping system. The layout process is called after measure ment. The four parameters are their relative position in the parent view. If it is ViewGroup, the relative position of the child view should be determined according to its position and layout attributes, and the child view should be laid. Out process, layout sub-view needs to rewrite onLayout.
1. Overall layout process
Let's start with an overall flow chart.
First, the setFrame method is invoked to set and determine whether the new ltrb has changed. If there is a change, the return value is ltrb changed. If there is a change, it will return true, add DRAWN flag to view, indicating that it will be redrawn. Calculate the new width (right-left, bottom-top). If the width changes, it needs to re-invalidate the original area. And call onSizeChanged callback (View can override this method for interception); finally update the value of ltrb. If the setFrame finds a change in location (returning true) or the view has a LAYOUT_REQUIRED flag (requiring rearrangement), call the onLayout method, then call back the OnLayout ChangeListener added to the view, and finally cancel the flags.
public void layout(int l, int t, int r, int b) { ... 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);//Update ltrb to see if the location is changed if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {//Change of location or need to be redesigned onLayout(changed, l, t, r, b);//Implementing Layout Sub view by Rewriting ViewGroup Subclass mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;//Cancellation of flags //Call listener 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); } } } //Cancellation of flags mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; } protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {//Is there any change in location? changed = true; // Adding markers that need to be redrawn int 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);//See if width and height change // Whether to refresh the old area invalidate(sizeChanged); //Update new location mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); mPrivateFlags |= PFLAG_HAS_BOUNDS; if (sizeChanged) {//Call this method to notify size changes sizeChange(newWidth, newHeight, oldWidth, oldHeight); } ... } return changed; }
2. Common onLayout implementations
The onLayout() method is implemented by a subclass of ViewGroup. As can be seen from the definition of the ViewGroup class as an abstract method, the onLayout() method is used to lay out the position of its sub view. Here are some common container layouts to illustrate how this is generally implemented.
1. onLayout of FrameLayout
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); // Computing four padding s of FrameLayout final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); //Traversing child for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) {//GONE does not compute final LayoutParams lp = (LayoutParams) child.getLayoutParams(); //The actual width and height obtained after measure ment 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; //According to the gravity attribute in the horizontal direction (in this case, the gravity attribute of FrameLayout) switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL://For example, if you want all child view s to be in the middle, you need to change the child's left to the child's left after the child's frame Layout level is in the middle. 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; } //According to the gravity attribute in the vertical direction (in this case, the gravity attribute of FrameLayout), similar to the above will not be repeated. 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; } child.layout(childLeft, childTop, childLeft + width, childTop + height);//Call the layout of the child view to lay out the child view } } }
The essential process is to determine the relative ltrb position in the parent view according to the child's width and alignment, and to call the child's layout for layout.
2. On Layout of Linear Layout
Like the measure ment process of Linear Layout, layout can be divided into vertical or horizontal layouts. Here we only introduce the vertical layouts Vertical method.
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; // Maximum width that a child view can occupy 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; //The top position of the child is determined by gravity attribute in the vertical direction of LinearLayout switch (majorGravity) { case Gravity.BOTTOM://For example, BOTTOM stands for the bottom, so put the top of the child at the bottom minus mTotalLength (mTotalLength variable is the total height calculated when measure) // 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; } //Traversing child 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;//This gravity is the android:layout_gravity in the layout attribute, representing the alignment in the parent layout, as explained below. if (gravity < 0) {//If the child has no alignment, use LinearLayout alignment for the child view. gravity = minorGravity; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL://In the middle of the level, move the child's left to the left of the center. 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);//Actually, it calls the child.layout method to lay out the sub-view. childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);//Update childTop so that the top of the next view is calculated from the next time i += getChildrenSkipCount(child, i); } } }
The essential process is to traverse the child, set the child's left and top according to its alignment method (designated by oneself or LinearLayout), update the top to make it vertically aligned, and then call the child's layout method for layout. One point needs to be explained:
- There are two gravities. The first one is a mGravity variable of LinearLayout itself. It refers to android:gravity in layout attributes. This attribute is generally the alignment within view itself. For example, TextView's attribute refers to the alignment of its internal text; the second one is gravity of LayOutm of child, which is the alignment of its internal text. It refers to android:layout_gravity in the layout attribute, which refers to the alignment in the parent view, which is used by the parent view for typesetting, so there is a layout_prefix, which is parsed into LayoutParam.
3. On Layout of Relative Layout
Relative Layout's measurement process is quite special. Because it needs to use specific position to calculate relative position in measurement, the ltrb of child has been obtained and stored in Layout Param after measurement, so its layout is very simple. It can call the child's layout method directly.
The measure ment process can be referred to. Series of articles
protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { RelativeLayout.LayoutParams st = (RelativeLayout.LayoutParams) child.getLayoutParams(); child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);//Direct call is enough. } } }