preface

Series of articles:

  • Android custom View Measure process
  • Android custom View Layout process
  • Android custom View Draw process (part 1)

In the previous article: Measure process of Android custom View, we analyzed Measure process. This time, we will uncover the mysterious veil of Layout process connecting the previous and the next. Through this article, you will learn:

1, about Layout simple analogy 2, a simple Demo 3, View Layout process 4, ViewGroup Layout process 5, View/ViewGroup common methods analysis 6, why say Layout is a link between the function of the preceding and the following

Simple analogy about Layout

In the metaphor of the previous article, we said:

Lao Wang assigned specific areas of fertile land to his three sons, Wang (Wang’s son: Xiao Xiao Wang), the second wang and the third Wang, and the three sons (Xiao Xiao Wang) also confirmed their own requirements of fertile land. This is: since the Measure process knows the size of the good land allocated to each child and grandchild, which land is allocated to them, to the side, to the middle, or to other places? Who shall we share it with first? Lao Wang wants to come to this house in the order of time (corresponding to the order of addView). Your Majesty is his eldest son, so it will be allocated to him first. Therefore, from the far left, 3 mu of land will be allocated to your Majesty. Now it is the turn of the second king. Since you have already allocated 3 mu on the left side, the 5 mu for the second king can only be divided from the right side of you, and the rest will be divided among the three Kings. This is the ViewGroup onLayout procedure. Your Majesty will take the boundary of the fertile land designated by Lao Wang and record the coordinates of the boundary (left, up, right and down). The king then told his son xiao Xiao Wang, “Your father is selfish. He can’t give you all the 5 mu of land he inherited from his grandfather. I will keep some for my retirement. If the second king thinks at the beginning of the survey, “I don’t want to get too close to the king’s and the third king’s fields,” then the old king will leave a gap between the king’s, the third king’s and the second king’s fields. This is the setup: margin process

A simple Demo

Custom ViewGroup

public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context) { super(context); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int usedWidth = 0; int maxHeight = 0; int childState = 0; For (int I = 0; i < getChildCount(); i++) { View childView = getChildAt(i); MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams(); measureChildWithMargins(childView, widthMeasureSpec, usedWidth, heightMeasureSpec, 0); usedWidth += layoutParams.leftMargin + layoutParams.rightMargin + childView.getMeasuredWidth(); maxHeight = Math.max(maxHeight, layoutParams.topMargin + layoutParams.bottomMargin + childView.getMeasuredHeight()); childState = combineMeasuredStates(childState, childView.getMeasuredState()); } usedWidth += getPaddingLeft() + getPaddingRight(); maxHeight += getPaddingTop() + getPaddingBottom(); setMeasuredDimension(resolveSizeAndState(usedWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); } @Override protected void onLayout(boolean changed, int l, int t, int r, ParentLeft = getPaddingLeft(); parentLeft = getPaddingLeft(); int left = 0; int top = 0; int right = 0; int bottom = 0; For (int I = 0; i < getChildCount(); i++) { View childView = getChildAt(i); MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams(); left = parentLeft + layoutParams.leftMargin; right = left + childView.getMeasuredWidth(); top = getPaddingTop() + layoutParams.topMargin; bottom = top + childView.getMeasuredHeight(); Childview. layout(left, top, right, bottom); ParentLeft += right; } } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MyLayoutParam(getContext(), attrs); } static class MyLayoutParam extends MarginLayoutParams {public MyLayoutParam(Context c, AttributeSet attrs) { super(c, attrs); }}Copy the code

This ViewGroup overwrites the onMeasure(xx) and onLayout(xx) methods:

  • OnMeasure (XX) measures the size of the sub-layout and determines its own size based on the measurement result of the sub-layout
  • OnLayout (XX) Places child layout positions

Also, when layout execution is complete, clear the PFLAG_FORCE_LAYOUT flag, which affects whether the Measure procedure needs to execute onMeasure.

The custom View

public class MyView extends View { public MyView(Context context) { super(context); } public MyView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.GREEN); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int defaultSize = 100; setMeasuredDimension(resolveSize(defaultSize, widthMeasureSpec), resolveSize(defaultSize, heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); }}Copy the code

This View overwrites the onMeasure(xx) and onLayout(xx) methods:

  • OnMeasure (XX) measures itself and records its size
  • OnLayout (xx) does nothing

Add a sublayout for MyViewGroup

<? The XML version = "1.0" encoding = "utf-8"? > <com.fish.myapplication.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/myviewgroup" android:layout_width="match_parent" android:layout_height="100dp" android:layout_gravity="center_vertical" android:background="#000000" android:paddingLeft="10dp" tools:context=".MainActivity"> <com.fish.myapplication.MyView android:layout_width="wrap_content" android:layout_height="wrap_content"> </com.fish.myapplication.MyView> <Button android:layout_marginLeft="10dp" android:text="hello Button" android:layout_width="match_parent" android:layout_height="wrap_content"> </Button> </com.fish.myapplication.MyViewGroup>Copy the code

MyViewGroup added MyView, Button two controls, the final run effect is as follows:





The View Layout process

View.layout(xx)

Similar to Measure procedures, the bridge between ViewGroup onLayout(XX) and View onLayout(XX) is View Layout(XX).

#View.java public void layout(int l, int t, int r, Int b) {//PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT may set if ((mPrivateFlags3 &) when measured 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)? Boolean changed = isLayoutModeOptical(mParent)? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); / / coordinates change or the need to layout / / PFLAG_LAYOUT_REQUIRED is set up after the Measure tag if (changed | | (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) = = PFLAG_LAYOUT_REQUIRED) {// Call onLayout and pass in the coordinates onLayout(changed, L, T, r, b) passed by the parent layout; . // Clear the request layout flag mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; / / to monitor onLayoutChange callback, through setting ListenerInfo addOnLayoutChangeListener 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); } } } final boolean wasLayoutValid = isLayoutValid(); // Clear the mandatory layout mark, which determines whether onMeasure is required when measured; mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; . } public static Boolean isLayoutModeOptical(Object o) { Android :layoutMode="opticalBounds" or Android :layoutMode="clipBounds" // Returns true, Return o instanceof ViewGroup && ((ViewGroup) o).islayOutModeOptical (); } private boolean setOpticalFrame(int left, int top, int right, Insets parentInsets = mParent instanceof View? ((View) mParent).getOpticalInsets() : Insets.NONE; Insets childInsets = getOpticalInsets(); // Change the coordinate value again, Return setFrame(left + parentInsets. Left - childInsets. Left, top + parentInsets. right + parentInsets.left + childInsets.right, bottom + parentInsets.top + childInsets.bottom); }Copy the code

As you can see, the setFrame(xx) method is eventually called.

#View.java 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; PFLAG_DRAWN int drawn = mPrivateFlags & PFLAG_DRAWN; Int oldWidth = mright-mleft; int oldHeight = mBottom - mTop; int newWidth = right - left; int newHeight = bottom - top; Bool sizeChanged = (newWidth! = oldWidth) || (newHeight ! = oldHeight); // Different, walk inValidate, and finally execute Draw process inValidate (sizeChanged); // record the new coordinate value mLeft = left; mTop = top; mRight = right; mBottom = bottom; / / set the coordinates to RenderNode mRenderNode. SetLeftTopRightBottom (mLeft, mTop, mRight, mBottom); / / tag has been layout mPrivateFlags | = PFLAG_HAS_BOUNDS; SizeChange {if (sizeChanged) {sizeChange(newWidth, newHeight, oldWidth, oldHeight); }... } return changed; }Copy the code

For Measure process, onMeasure(XX) records the size value, while for Layout process, coordinate value is recorded in Layout (XX), specifically in setFrame(XX). This method has two key points:

Record the new coordinate values in the RenderNode member variables mLeft, mTop, mRight, and mBottom. When the Draw process is called, the Canvas will Draw from the RenderNode position

View.onLayout(xx)

#View.java
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }
Copy the code

View.onlayout (xx) is an empty implementation. The layout(xx) and onLayout(xx) declarations show that both methods can be overwritten. See if ViewGroup overwrites them.

ViewGroup Layout process

ViewGroup.layout(xx)

#ViewGroup.java @Override public final void layout(int l, int t, int r, int b) { if (! mSuppressLayout && (mTransition == null || ! MTransition. IsChangingLayout ())) {/ / not been delayed, or animation not changing coordinates the if (mTransition! = null) { mTransition.layoutChange(this); } // View. Layout (xx) super.layout(l, t, r, b); Is delayed} else {/ /, the tag set, the animation is completed, according to the sign bit requestLayout re-launch the layout process mLayoutCalledWhileSuppressed = true; }}Copy the code

Viewgroup.layout (xx) rewrites layout(xx), but only makes a simple judgment, and finally calls view.layout (xx).

ViewGroup.onLayout(xx)

#ViewGroup.java
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);
Copy the code

This overwrites onLayout to an abstract method, meaning that classes that inherit from ViewGroup must override the onLayout(xx) method. Let’s take FrameLayout as an example and analyze what its onLayout(xx) does.

FrameLayout.onLayout(xx)

#FrameLayout.java 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(); / / outlook padding, mean layout is put do not encroach on the final position 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); // Go without layout if (child.getvisibility ()! // getLayoutParams final framelayout.layoutparams lp = (framelayout.layoutparams) child.getlayoutparams (); 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 layoutDirection = getLayoutDirection(); / / level of reverse Gravity final int absoluteGravity = Gravity. GetAbsoluteGravity (Gravity, layoutDirection); // Gravity final int verticalGravity = Gravity & gravity_mask; switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: // If the child layout is horizontally centered, its horizontal starting point // subtracting the remaining position of the parent layout // Combined with the width of the child layout, so that the child layout is centered in the remaining position // Then take into account the margin of the child layout. Margin childLeft = parentLeft + (parentRight - parentleft-width) / 2 + lp.leftmargin-lp.rightMargin; break; Case Gravity.RIGHT: // change the horizontal start coordinate if (! forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } // Default is LEFT to right case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } 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 (childLeft, childTop, childLeft + width, childTop + height); }}}Copy the code

When framelayout.onLayout (XX) is used as a sub-layout, the starting coordinates are based on FrameLayout, and the position of the sub-layout is not recorded. Therefore, the position of the sub-layout may overlap, which is also the origin of FrameLayout Layout feature. Our previous Demo recorded the position of the previous sublayout in the horizontal direction, and the next one can only be placed after it, thus forming the horizontal function. Viewgroup.onlayout (xx); viewGroup.onLayout (xx); viewGroup.onLayout (xx)

View/ViewGroup common methods analysis

View.layout(xx), view.onLayout (XX), viewGroup.layout (XX), viewGroup.onLayout (xx), viewGroup.onLayout (xx), viewGroup.onLayout (xx) View.layout(xx)

Record placement coordinates in member variables and set values for RenderNode

View.onLayout(xx)

Empty implementation

ViewGroup.layout(xx)

Call the layout (xx)

ViewGroup.onLayout(xx)

Abstract methods, subclasses must be overridden. Subclass overrides need to calculate the placement of each sublayout and pass it to the sublayout

Which methods should the View/ViewGroup subclass override:

OnLayout (xx) and onLayout(XX) need not be overridden to compute position coordinates for the child layout inherited from View because it has no child layout to place

Graphically:

Why Layout is a link between the preceding and the following

Based on the above description, we find that the methods defined in Measure process and Layout process are similar:

measure(xx)<—–>layout(xx)

onMeasure(xx)<—–>onLayout(xx)

Their routines are similar: Measure(XX) and layout(XX) generally do not need to be rewritten. Measure(XX) calls onMeasure(XX), and layout(XX) sets coordinate values for the caller. If the ViewGroup: onMeasure(xx) traverses the sub-layout, and measures each sub-layout, and finally summarizes the results and sets its own measurement size; OnLayout (xx) iterates through the sub-layouts and sets the coordinates of each sub-layout. If View: onMeasure(XX), measure itself and store the measured size; OnLayout (XX) does nothing.

Bearing on the

The Measure procedure is more complex than the Layout procedure, but if you look at it carefully, you’ll see that it’s all about setting two member variables:

Set mMeasuredWidth and mMeasuredHeight

The Layout procedure, though relatively simple, is essentially about setting coordinate values

1, set up mLeft, mRight, mTop, mBottom these four values define a rectangular area. 2, mRenderNode setLeftTopRightBottom (mLeft, mTop, mRight, MBottom) sets the coordinates for RnederNode

Associate variables set by Measure with variables set by Layout:

MRight and mBottom are calculated by combining mLeft and mRight with mMeasuredWidth and mMeasuredHeight

In addition, the Measure procedure tells the Measure process that onLayout is required by setting the PFLAG_LAYOUT_REQUIRED flag, and the Layout procedure tells the Measure process that onMeasure is no longer required by clearing the PFLAG_FORCE_LAYOUT flag. This is where Layout comes in

Under the rev.

We know that a View needs to be drawn on a Canvas, and Canvas is scoped. For example, we use:

canvas.drawColor(Color.GREEN);
Copy the code

Where does Cavas start? For hardware rendering acceleration: It is the RenderNode coordinates that are set during Layout. For software drawing:

    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        canvas.translate(mLeft - sx, mTop - sy);
    }
Copy the code

Hardware rendering acceleration/software rendering will be discussed in future articles.

This is the function of Layout, which is the internal connection of Measure, Layout and Draw. Of course, the “loading” of Layout also needs to consider the influence of margin, gravity and other parameters. See the original Demo for details.

A classic problem

Measuredwidth ()/ measuredHeight/getWidth/getHeight

#View.java
    public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

    public final int getWidth() {
        return mRight - mLeft;
    }
Copy the code

GetWidth () : Specifies the true width of the View. GetWidth () defaults to 0 before the Layout process

1, rewrite the onSizeChanged (xx) method to get 2, registered the addOnLayoutChangeListener (xx), in onLayoutChange gain 3 (xx), rewrite the onLayout (xx) method

In the next chapter, we will analyze the process of Draw(). We will analyze the truth that “everything is drawn by Draw”

Android custom View Draw process (part 1)

This article is based on Android 10.0