My grandmother can understand the UI drawing process (next)!

Keywords: Android REST network xml

1. Preface

Last time, let's talk about the ViewRootImpl.performTraversals() method, which starts here and goes into the real View drawing process.First time classmates go next door first My grandmother can understand the UI drawing process (above)! Learn the preparatory knowledge, the rest of the students fasten their seatbelts and drive!

2.Measure

2.1MeasureSpec

We started our tour from performTraversals() and found that there was first a gadget called childWidthMeasureSpec

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);   
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

It can be guessed from the name that this is used to determine the method of measurement at the time of measurement.What do you mean by that?In Android, there are three ways to choose the size of a control, match_parent, wrap_content, and a specific value.Think about it. Your name is system wrap_content. How does it know how to wrap_content and how big is the content?How much space does the parent layout give it?

So now you need MeasureSpec.

In the Makesure process, the system converts View's LayoutParams into corresponding MeasureSpec s based on the rules imposed by the parent container, which determines the view's measurement width and height in onMeasure.

Let's look at this class in detail

 public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;
        ...

MeasureSpec is a 32-bit int value, with 2 bits high for mode and 30 bits low for size.

It is clear that MeasureSpec has three types

1.EXACTLY: The parent container has already measured the exact size required, which is also the final size of the child view
 - match_parent, exact value

2. ATMOST: The final size of the child view cannot exceed the size given by the parent container
-wrap_content 

3.UNSPECIFIED: Uncertain, internal use of source code
 - Typically in ScrollView, ListView

But what is the beginning of MODE_MASK = 0x3 << MODE_SHIFT?

This involves AND or NON-OPERATION. It's not human. There's nothing wrong with this sentence. They're for computers and programmers. Programmers really aren't human.

Start with the or ('|') operation, where you combine the mode l with the size.


Bit operation or.png

Next, with ('&'), MODE_MASK acts like a mask on the network, filtering out content, and here getting mode.


Bit Operations and.png

Last but not ("~&"), similar to the above is also filtering content, which is used to filter out size


Bit operation and non.png

It's still a humble Daoist here. I suggest you learn about the principles of computer composition. It's great to recommend "How Programs Run" (Day).I don't have a link to Taobao.

2.2.getRootMeasureSpec

Now let's look at int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width), and we need to look at the second parameter to find out where it came from:

 WindowManager.LayoutParams lp = mWindowAttributes;
final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
public LayoutParams() {
            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            ...
        }

As you can see from the code above, LP calls the parent class's constructor at initialization with a default value of LayoutParams.MATCH_PARENT.Now go back to getRootMeasureSpec(mWidth, lp.width) and see the complete code for this method

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

You can see that the MensureSpec of the child View is determined by the LayoutParams of the parent View, where there are three types that validate the previous summary of MeasureSpec.

At this point, the parameter we passed in is LayoutParams.MATCH_PARENT, so the returned child WidthMeasureSpec is MeasureSpec.EXACTLY.

2.3.View.measure()

Now that you have the parameters ready, let's take a look at performMeasure(childWidthMeasureSpec, childHeightMeasureSpec), which calls mView.measure(childWidthMeasureSpec, childHeightMeasureSpec), which is decorview, so the measure() method of view is ultimately called.

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        ...
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        ...

        if (forceLayout || needsLayout) {
            ...
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

             ...
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

It's a long way to go, so let's look from top to bottom.

First look at optical, which is the light-shading effect. Shadows also take up space when measuring.

Next, look at the mMeasureCache, which is used to process the measurement cache.Combined with the code that follows, you can see that before measuring, you try to take out the cache from the mMeasureCache, and after measuring, you place the measurement results in the cache.

Finally, onMeasure(widthMeasureSpec, heightMeasureSpec) is the focus.In the entire measure() method, we don't see the specific measurement code, because different View s have different measurement methods, which are determined by the subclasses themselves.

This is a typical template method pattern (one of the design patterns that will fill in holes later when the Android architecture is implemented), where measure() is a template, and since all controls ultimately inherit from View, only measure() needs to be implemented in View; onMeasure() needs to be customized by the child View, so the child View overrides the onMeasure() method.

2.4.View.onMeasure()

Before reading decorview's onMeasure(), let's look at View's onMeasure() method

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

Simple, just call setMeasuredDimension

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

You can see that the light-shading effect is also handled here, and setMeasuredDimensionRaw() is called last.

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

Here, we assign values to mMeasuredWidth and mMeasuredHeight.

Do you remember anything?Previously, we used to get the width and height of the control by getMeasuredXXX() in onCreate() method. The result failed. Why?Take getMeasuredHeight() as an example

public final int getMeasuredHeight() {
        return mMeasuredHeight & MEASURED_SIZE_MASK;
    }

This returns mMeasuredHeight, which is a series of complex calls made through ViewRootImpl in onResume() and is ultimately assigned to setMeasuredDimensionRaw() in View, so it is naturally not available in onCreate().

Back to the above method, where does this measuredWidth and measuredHeight come from by default?Let's see what getSuggestedMinimumWidth() did

  protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

This mMinWidth has to remember it, which is a key value when customizing controls.It is usually assigned a value, both in code and in XML.

2.5.FrameLayout.onMeasure()

Having said a lot of rubbish, now let's go back and look at DecorView's onMeasure() method.

Unfortunately, instead of making a specific measurement, super.onMeasure(widthMeasureSpec, heightMeasureSpec), or FrameLayout's onMeasure(), was called.

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        ...
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                ...
                }
            }
        }
        ...
}

Before looping, you get the number of subviews, and then you start measuring each subview to get its measurement width and height.Mainly through measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0) method.

protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

Are you a little familiar?Well, that's what performTraversals() does when it starts measuring.

First get the child WidthMeasureSpec and child HeightMeasureSpec, then complete the measurement by child.measure (child WidthMeasureSpec, child HeightMeasureSpec).Clearly this is an iterative process.

The difference is that performTraversals() gets the MeasureSpec of the root node, whereas here it gets the MesureSpec of the child View, so there are two factors to consider, the parent View and itself.

2.5.1.MeasureSpec Nine Brothers

Let's look at getChildMeasureSpec(), which has 3*3=9 cases.I will only cover three of them

  public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        ...
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

The code structure is clear. First, you determine the MesureSpec of the parent View, and then, if MeasureSpec.EXACTLY, you begin to determine the child Dimension of the child View.

1. If it is a specific >0 point value, assign the value directly to the child View and set the type to MeasureSpec.EXACTLY;
2. If it is LayoutParams.MATCH_PARENT, set the value to the size of the parent container, of type MeasureSpec.EXACTLY;
3. If it is LayoutParams.WRAP_CONTENT, set the value to the size of the parent container, of type MeasureSpec.AT_MOST;

The remaining six cases are similar, the code does not show, the picture summarized directly


getChildMeasureSpec method analysis.png

This table doesn't need to be remembered. Just think about it first, and you'll find that this is really the case.

2.6 Measure Summary

Sub View's measurements are finally done in measureChildWithMargins(). So much said, the first measurements of UI drawing are almost done. Let's summarize.
1. Measurements of View s
Call setMeasuredDimension() inside the onMeasure method to determine the size of the current View
2. Measurement of ViewGroup
2.1. Traversing the measurement Child, you can traverse the measurement in three ways: ChildmeasureChildWithMargins, measureChild, measureChildren
2.2, setMeasuredDimension determines the size of the current ViewGroup


Source measure ments flowchart for View tree.png

3.Layout

It took so much time to describe the Measure process before, and now it's easier to look back at Layout because they all follow the same routine.

Starting with performLayout(), call the layout() method directly, concise and clear

final View host = mView;
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

host is our decorview, so let's look at the most critical layout() method

public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & 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) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

      ...
    }

At first, you need to decide if you need to measure again based on flag.

Next, assign the upper left and lower right positions to oldL, oldT, oldR, oldB in turn

Continuing, boolean changed acts like cache in measure and is used to reduce layout operations.Here is a trinomial operator. Depending on whether there are shadows or not, we call different methods. Let's take setFrame(l, t, r, b) as an example

  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;
            ...
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

            // Invalidate our old position
            invalidate(sizeChanged);

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
            ...
            if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }
          ...
        }
        return changed;
    }

Well, is it fairly easy to understand?This is a very common caching strategy.

Finally, layout() is responsible for calling onLayout(changed, l, t, r, b), in the words of Sister Zhiling, "This is an empty box"

 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

Take a look at onLayout() of ViewGroup, and more importantly, it's an abstract method.That is, each View's onLayout() needs to be implemented by itself.Think about the same thing. What kind of person do you want to be? Don't you has the final say?
Here, for the movie experience, let's take FrameLayout as an example to see what his onLayout() does

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

First is to lay out the child View

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();
        ...
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (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;
                }
               ...
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

The layoutChildren() itself is also straightforward, taking the number of child controls, looping through them, getting the width and height in turn, judging the situation, and calling the layout method of the child controls.

That's it. Layout is finished.

4.Draw

With Measure and Layout in place, Draw can understand more easily.
In accordance with international practice, we look at ViewRootImpl.performDraw()

private void performDraw() {
 ...
draw(fullRedrawNeeded);
...

ViewRootImpl.draw() then calls ViewRootImpl.drawSoftware(), then mView.draw(canvas).We know that mView is DecorView, and this method eventually goes into the View.draw() method.

public void draw(Canvas canvas) {
        ...
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

It's so thoughtful, these notes have already explained everything.

To be specific, first look at the first step and draw the background.

 // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

Here's a sign called dirtyOpaque.When customizing ViewGroup s, onDraw methods are generally not called unless background is set.Think about it carefully. I don't have a background. There's nothing to draw.This is also one reason for overdrawing.

To expand a little, why does LinearLayout draw faster than RelativeLayout?They actually spend the same amount of time on measure ments and layout s. The difference is draw, RelativeLayout draws from left to right, up to down, and LinearLayout only draws once.

The third step draws content the same way. Normally ViewGroup s do not have content themselves, but only childView.

 // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

Finally, look at drawing the sub View, which is an empty method left to future generations to inherit.

/**
     * Called by draw to draw the child views. This may be overridden
     * by derived classes to gain control just before its children are drawn
     * (but after its own view has been drawn).
     * @param canvas the canvas on which to draw the view
     */
    protected void dispatchDraw(Canvas canvas) {

    }

We don't usually greet him, draw s are more often used in custom views, that is, simply override the onDraw() method.

So far, Draw has finished, the whole UI drawing is over!

5. Practice is the only criterion for testing truth

After that, let's look at an application.

5.1 ScrollView incompatibility with ListView

As you know, when you nest a ListView in a ScrollView, the ListView only shows the first row. Why?Let's walk into the inner world of ListView.onMeasure()

if (heightMode == MeasureSpec.UNSPECIFIED) {
            heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                    getVerticalFadingEdgeLength() * 2;
        }

What is this?When heightMode l is MeasureSpec.UNSPECIFIED, the height of ListView should only measure the height of the first child View!

Take a look at ScrollView, where he rewrote measureChildWithMargins

final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
                MeasureSpec.UNSPECIFIED);

Sodri temple!ScrollView sets the HahtMeasureSpec of the child View to MeasureSpec.UNSPECIFIED, and the ListView gets confused here.

As for the solution, rewriting ListView or ScrollView will work, does it feel clear?

6. Summary

Really even your grandmother can understand!

Finish spraying flowers~

Posted by WeAnswer.IT on Mon, 10 Jun 2019 09:54:49 -0700