The principle of onLayout

final boolean didLayout = layoutRequested && (! mStopped || mReportNextDraw); boolean triggerGlobalLayoutListener = didLayout || mAttachInfo.mRecomputeGlobalAttributes; if (didLayout) { performLayout(lp, mWidth, mHeight); if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) ! = 0) { host.getLocationInWindow(mTmpLocation); mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1], mTmpLocation[0] + host.mRight - host.mLeft, mTmpLocation[1] + host.mBottom - host.mTop); host.gatherTransparentRegion(mTransparentRegion); if (mTranslator ! = null) { mTranslator.translateRegionInWindowToScreen(mTransparentRegion); } if (! mTransparentRegion.equals(mPreviousTransparentRegion)) { mPreviousTransparentRegion.set(mTransparentRegion); mFullRedrawNeeded = true; try { mWindowSession.setTransparentRegion(mWindow, mTransparentRegion); } catch (RemoteException e) { } } } } if (triggerGlobalLayoutListener) { mAttachInfo.mRecomputeGlobalAttributes = false;  mAttachInfo.mTreeObserver.dispatchOnGlobalLayout(); } // Distribute internal insets, we don't care about ellipsis logic for now... }Copy the code

The subsequent traversals of formtraversals are divided into the following aspects:

  • 1. Determine whether the current View needs to be repositioned. If the performTraversals method is executed using requestLayout, layoutRequested is true; Call performLayout to rearrange the layout.
  • 2. If the requestTransparentRegion method is invoked and the transparent area needs to be recalculated, the gatherTransparentRegion method is invoked to recalculate the transparent area. If the current and previous transparent regions are found to have changed, update the REGIONS on the WMS side through WindowSession.

This is usually the case where there is a SurfaceView. Because SurfaceView has its own system that communicates to SF for rendering. Android doesn’t need to include SurfaceView in a hierarchy, it needs to be treated as transparent and optimized as an unnecessary hierarchy.

I’m going to go back to the core and focus on what does performLayout do?

ViewRootImpl performLayout

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        if (host == null) {
            return;
        }


        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

            mInLayout = false;
            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {

                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
                if (validLayoutRequesters != null) {

                    mHandlingLayoutInLayoutRequest = true;

                    int numValidRequests = validLayoutRequesters.size();
                    for (int i = 0; i < numValidRequests; ++i) {
                        final View view = validLayoutRequesters.get(i);
                        view.requestLayout();
                    }
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                    mHandlingLayoutInLayoutRequest = false;


                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                    if (validLayoutRequesters != null) {
                        final ArrayList<View> finalRequesters = validLayoutRequesters;

                        getRunQueue().post(new Runnable() {
                            @Override
                            public void run() {
                                int numValidRequests = finalRequesters.size();
                                for (int i = 0; i < numValidRequests; ++i) {
                                    final View view = finalRequesters.get(i);

                                    view.requestLayout();
                                }
                            }
                        });
                    }
                }

            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
    }
Copy the code

In fact, the entire core is this code:

            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
Copy the code

This code will start the process of traversing the View tree layout, that is, the View layout process.

After the layout process is processed, it will continue to check whether there are views in the measurement and whether there are other View requests to refresh in the process request. If the request is made, the View will be saved in mLayoutRequesters object. Take it out and measure and place it again.

Remember that the root layout is the DecorView is the Layout method. Since both DecorView and FrameLayout have rewrite layouts, let’s look at the ViewGroup layout.

ViewGroup layout

public final void layout(int l, int t, int r, int b) { if (! mSuppressLayout && (mTransition == null || ! mTransition.isChangingLayout())) { if (mTransition ! = null) { mTransition.layoutChange(this); } super.layout(l, t, r, b); } else { // record the fact that we noop'd it; request layout when transition finishes mLayoutCalledWhileSuppressed = true; }}Copy the code

The layout method of the View has two conditions:

  • 1. MSuppressLayout is false, that is, the suppression Layout method is not set
  • 2. The mTransition LayoutTransition animation is empty or does not change.

The Android animation API provides LayoutTransition, which is used to add and remove custom attribute animations to child views.

Remember that the layout parameters passed in from the DecorView represent where the View can be placed left, top, right, and bottom. But that doesn’t mean the View is placed there.

View layout

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); if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == null) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); } } else { mRoundScrollbarRenderer = null; } 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); } } } final boolean wasLayoutValid = isLayoutValid(); mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; if (! wasLayoutValid && isFocused()) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; if (canTakeFocus()) { clearParentsWantFocus(); } else if (getViewRootImpl() == null || ! getViewRootImpl().isInLayout()) { clearFocusInternal(null, /* propagate */ true, /* refocus */ false); clearParentsWantFocus(); } else if (! hasParentWantsFocus()) { clearFocusInternal(null, /* propagate */ true, /* refocus */ false); } } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) ! = 0) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; View focused = findFocus(); if (focused ! = null) { // Try to restore focus as close as possible to our starting focus. if (! restoreDefaultFocus() && ! hasParentWantsFocus()) { focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false); } } } if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) ! = 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(true); }}Copy the code

It can be divided into the following steps:

  • 1. Check whether PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT is enabled. This flag bit is turned on when the onMeasure step finds that the size passed by the original parent container is unchanged and sets the old measurement result in the View. In the layout step, onMeasure will be called once to continue traversing and measuring the size of the underlying child View.
  • 2. Determine whether the isLayoutModeOptical is enabled with optical edge mode. On setOpticalFrame for edge Settings in four directions, otherwise setFrame processing. Used to determine whether the values in the four directions need to be updated.
  • 3. If the size or position changes, perform onLayout callback. Subclasses typically override this method for further placement Settings.
  • 4. Initialize the RoundScrollbarRenderer object if the slider needs to be displayed. This object is really just a custom View that encapsulates how to draw a slider.
  • 5. Callback OnLayoutChangeListener callback that has been listening for Layout changes.
  • 6. The current layout behavior is valid when no other layout behavior is performed. If the layout behavior is invalid and the View gains focus, clear. If you want to request focus at this point, clear the focus.
  • 7. Notify AllFillManager for processing.

Let’s focus here on what the setFrame method does.

setFrame

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 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); invalidate(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); mPrivateFlags |= PFLAG_HAS_BOUNDS; if (sizeChanged) { sizeChange(newWidth, newHeight, oldWidth, oldHeight); } if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView ! = null) { mPrivateFlags |= PFLAG_DRAWN; invalidate(sizeChanged); invalidateParentCaches(); } mPrivateFlags |= drawn; mBackgroundSizeChanged = true; mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo ! = null) { mForegroundInfo.mBoundsChanged = true; } notifySubtreeAccessibilityStateChangedIfNeeded(); } return changed; }Copy the code

Take setFrame as an example to see the core idea. It’s actually quite simple:

  • 1. Compare the values in the four directions of upper left, lower right and lower right. If there is a change, update the size of the four directions and determine whether the entire area to draw has changed. Call Invalidate with sizechange as the parameter to refresh onDraw.
  • 2. Get the hardware-rendered object mRenderNode and set the position of the render point.
  • 3. Call the onSizeChange callback with the sizeChange method:
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) { onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); if (mOverlay ! = null) { mOverlay.getOverlayView().setRight(newWidth); mOverlay.getOverlayView().setBottom(newHeight); } if (! sCanFocusZeroSized && isLayoutValid() // Don't touch focus if animating && ! (mParent instanceof ViewGroup && ((ViewGroup) mParent).isLayoutSuppressed())) { if (newWidth <= 0 || newHeight <= 0) { if (hasFocus()) { clearFocus(); if (mParent instanceof ViewGroup) { ((ViewGroup) mParent).clearFocusedInCluster(); } } clearAccessibilityFocus(); } else if (oldWidth <= 0 || oldHeight <= 0) { if (mParent ! = null && canTakeFocus()) { mParent.focusableViewAvailable(this); } } } rebuildOutline(); }Copy the code

If the current focus is not ViewGroup and the new focus width is less than 0, the focus is cleared and the AccessibilityService is notified. If the new width is greater than 0, the parent container is notified that focus is available. Finally, rebuild the outer box.

  • 4. Check that the mGhostView is not empty and the current View is visible. Then issue draw refresh command to mGhostView. And notify the parent container to refresh as well. Here mGhostView is actually an overlay layer, similar to ViewOverLay.

So I’m done with the onLayout of the View and ViewGroup. But it’s not over yet. Remember that we are now analyzing a DecorView. So let’s see what the DecorView does in onLayout.

DecorView onLayout

protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); getOutsets(mOutsets); if (mOutsets.left > 0) { offsetLeftAndRight(-mOutsets.left); } if (mOutsets.top > 0) { offsetTopAndBottom(-mOutsets.top); } if (mApplyFloatingVerticalInsets) { offsetTopAndBottom(mFloatingInsets.top); } if (mApplyFloatingHorizontalInsets) { offsetLeftAndRight(mFloatingInsets.left); } updateElevation(); mAllowUpdateElevation = true; if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) { getViewRootImpl().requestInvalidateRootRenderNode(); }}Copy the code
  • 1. After calling the onLayout method of FrameLayout, determine the position of each sub-view.

  • 2. The getOutsets method gets the mOutSet area in mAttachInfo. From the print in the previous article, the area of the mOutsets actually refers to the top four padding sizes of the current screen. If both the left and right sides are greater than 0, call offsetLeftAndRight and offsetTopAndBottom to set it.

  • 3. If mApplyFloatingVerticalInsets or mApplyFloatingHorizontalInsets to true that DecorView themselves need to deal with a onApplyWindowInsets callback. If the FLAG_LAYOUT_IN_SCREEN bit is turned off in non-full-screen mode and the width or height is WRAP_CONTENT mode, add the systemwindowInset padding value to the horizontal and vertical axes.

public WindowInsets onApplyWindowInsets(WindowInsets insets) { final WindowManager.LayoutParams attrs = mWindow.getAttributes(); mFloatingInsets.setEmpty(); if ((attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0) { if (attrs.height == WindowManager.LayoutParams.WRAP_CONTENT) { mFloatingInsets.top = insets.getSystemWindowInsetTop(); mFloatingInsets.bottom = insets.getSystemWindowInsetBottom(); insets = insets.inset(0, insets.getSystemWindowInsetTop(), 0, insets.getSystemWindowInsetBottom()); } if (mWindow.getAttributes().width == WindowManager.LayoutParams.WRAP_CONTENT) { mFloatingInsets.left = insets.getSystemWindowInsetTop(); mFloatingInsets.right = insets.getSystemWindowInsetBottom(); insets = insets.inset(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0); } } mFrameOffsets.set(insets.getSystemWindowInsets()); insets = updateColorViews(insets, true /* animate */); insets = updateStatusGuard(insets); if (getForeground() ! = null) { drawableChanged(); } return insets; }Copy the code
  • 4. When everything is laid out, call updateElevation to update the shadow area of the form.
// The height of a window which has focus in DIP. private final static int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20; // The height of a window which has not in DIP. private final static int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5; private void updateElevation() { float elevation = 0; final boolean wasAdjustedForStack = mElevationAdjustedForStack; final int windowingMode = getResources().getConfiguration().windowConfiguration.getWindowingMode(); if ((windowingMode == WINDOWING_MODE_FREEFORM) && ! isResizing()) { elevation = hasWindowFocus() ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP; if (! mAllowUpdateElevation) { elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP; } elevation = dipToPx(elevation); mElevationAdjustedForStack = true; } else if (windowingMode == WINDOWING_MODE_PINNED) { elevation = dipToPx(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP); mElevationAdjustedForStack = true; } else { mElevationAdjustedForStack = false; } if ((wasAdjustedForStack || mElevationAdjustedForStack) && getElevation() ! = elevation) { mWindow.setElevation(elevation); }}Copy the code

As you can see during this process, if the form is in Freedom mode (that is, more like a computer form that can be dragged) and is not being dragged and resized, the size of the shadow in the four directions will be determined based on whether the form is focused. Note that if there is no focus, it is 5; if there is focus, it is 20. Here, the distance is not to increase the size of the current measurement when measuring, but to continue to occupy the content space in the measured size, which is equivalent to setting the padding value.

If the window is WINDOWING_MODE_PINNED or WINDOWING_MODE_FREEFORM mode, and elevation has changed, set the Surface Insets with phonewindow.setelevation.

  • 5. The last call requestInvalidateRootRenderNode, notify the ViweRootImpl hardware rendering ThreadRenderer to refresh the map object.

In this process, there are a series of functions that update the offset of the placement, especially offsetLeftAndRight, to see how it is calculated.

offsetLeftAndRight

public void offsetLeftAndRight(int offset) { if (offset ! = 0) { final boolean matrixIsIdentity = hasIdentityMatrix(); if (matrixIsIdentity) { if (isHardwareAccelerated()) { invalidateViewProperty(false, false); } else { final ViewParent p = mParent; if (p ! = null && mAttachInfo ! = null) { final Rect r = mAttachInfo.mTmpInvalRect; int minLeft; int maxRight; if (offset < 0) { minLeft = mLeft + offset; maxRight = mRight; } else { minLeft = mLeft; maxRight = mRight + offset; } r.set(0, 0, maxRight - minLeft, mBottom - mTop); p.invalidateChild(this, r); } } } else { invalidateViewProperty(false, false); } mLeft += offset; mRight += offset; mRenderNode.offsetLeftAndRight(offset); if (isHardwareAccelerated()) { invalidateViewProperty(false, false); invalidateParentIfNeededAndWasQuickRejected(); } else { if (! matrixIsIdentity) { invalidateViewProperty(false, true); } invalidateParentIfNeeded(); } notifySubtreeAccessibilityStateChangedIfNeeded(); }}Copy the code

This procedure will evaluate if offset is not equal to zero. If the RenderNode determines that there is a unit change matrix (we won’t talk about this matrix for a moment, but we’ll cover the mechanics of hardware rendering).

  • 1. If hardware acceleration is detected, call invalidateViewProperty to refresh.
  • 2. The logic of software rendering is essentially the same without hardware acceleration.

You can see here that if offset is less than 0, increase the size of minLeft. If offset is greater than 0, increment maxRight. Calculate the refreshed region:

Offset >0 maxRight = maxRight + mRight offset<0 minLeft = minLeft + mLeft Refresh horizontal range: maxright-minleft

Calculate the area that needs to be refreshed and send the refresh command through the invalidateChild that gets the parent layout. The principle is as follows:

  • 3. If there is no unit transformation matrix, call invalidateViewProperty to send the refresh command
  • 4. Add offset to mLeft and mRight. Synchronize left and right data to mRenderNode.
  • 5. If you open the hardware acceleration, call again invalidateViewProperty, and invoke invalidateParentIfNeededAndWasQuickRejected refused to traverse the refresh.
  • 6. Turn hardware acceleration off. If there is no unit transformation matrix, call invalidateViewProperty. Then call invalidateParentIfNeeded.

You can see that several methods are called frequently during this process:

  • 1.invalidate
  • 2.invalidateChild
  • 3.invalidateViewProperty
  • 4.invalidateParentIfNeededAndWasQuickRejected
  • 5.invalidateParentIfNeeded

These methods determine which areas to draw to update.

FrameLayout onLayout

 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();

        final 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);
            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;
                }

                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        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;
                }

                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);
            }
        }
    }
Copy the code

FrameLayout’s onLayout method is simple. It’s essentially iterating through every visible child View to handle its gravity. It can be divided into horizontal axis and vertical axis for processing:

  • Gravity is CENTER_HORIZONTAL, gravity horizontal, gravity horizontal

Each child left = left position of the parent View – (father’s width – child’s width) / 2 + child’s marginLeft – Child’s marginRight

Keep your child centered.

  • 2. Gravity. RIGHT:

Left of child = right of parent View – child width – child marginRight

Ensure that the child is placed on the right or on the right.

  • 3.Gravity.LEFT

Child’s left = parent View’s left + child’s marginLeft

Same thing in the vertical direction.

Finally, the layout process of each child is iterated through child.layout.

LinearLayout onLayout

protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l, t, r, b); }}Copy the code
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; // Space available for child 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; switch (majorGravity) { case Gravity.BOTTOM: childTop = mPaddingTop + bottom - top - mTotalLength; break; case Gravity.CENTER_VERTICAL: childTop = mPaddingTop + (bottom - top - mTotalLength) / 2; break; case Gravity.TOP: default: childTop = mPaddingTop; break; } for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { ... } 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; if (gravity < 0) { gravity = minorGravity; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: 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); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } } private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); }Copy the code

You can see that the logic here is very similar to FrameLayout, also dealing with gravity. This method has been used to measure the four positions of the LinearLayout in the parent container layout method to further place the child Views in the LinearLayout.

Gravity in both directions is handled separately in the logic of vertical placement. First, let’s look at the processing of gravity in the vertical direction:

  • 1.Gravity.BOTTOM

The top of each child = paddingTop + The total height of the LinearLayout’s bottom-LinearLayout’s top-LinearLayout

By placing subviews up from the bottom of the LinearLayout.

  • 2.Gravity.CENTER_VERTICAL

Each child’s top = mPaddingTop + (LinearLayout’s bottom-LinearLayout’s top-MTotallength) / 2

Set the sub-view of the LinearLayout by calculating the location of the LinearLayout.

  • 3.Gravity.TOP

Top of each child = mPaddingTop

You can see that Gravity is placed in the vertical direction, and handling Gravity in the vertical direction only handles all subviews. It doesn’t add up.

Gravity in the horizontal direction is processed in the vertical direction:

Width of each child childSpace = Width of LinearLayout – paddingLeft – mPaddingRight

  • 1.Gravity.CENTER_HORIZONTAL:

PaddingLeft = ((childSpace – childWidth) / 2)\

  • lp.leftMargin – lp.rightMargin;
  • 2.Gravity.RIGHT:

Left of each child = childRight – childWidth-lp.rightMargin

  • 3.Gravity.LEFT:

Left of each child = paddingLeft + lp.leftmargin;

In the process of horizontal processing, the topMargin of each sub-view is continuously accumulated, and the layout method of the sub-view is called to carry out the placement process of the sub-view. After measuring the sub-view, the height, bottomMargin and offset of the sub-view are accumulated.

RelativeLayout onLayout

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); }}}Copy the code

Within the RelativeLayout, the layout logic has already been handled in the onMeasure’s 7 passes. Simply iterate through the layout method of each child View.

The family of invalidate methods

You can see that methods with invalidate meaning are used many times during the onLayout process.

  • 1.invalidate
  • 2.invalidateChild
  • 3.invalidateViewProperty
  • 4.invalidateParentIfNeededAndWasQuickRejected
  • 5.invalidateParentIfNeeded

This is essentially a way of telling the Android rendering system that an area has been set to invalid, that is, a dirty area that has changed and needs to be redrawn. Let’s look at the invalidate method first.

View invalidate

public void invalidate() { invalidate(true); } public void invalidate(boolean invalidateCache) { invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); } private boolean skipInvalidate() { return (mViewFlags & VISIBILITY_MASK) ! = VISIBLE && mCurrentAnimation == null && (! (mParent instanceof ViewGroup) || ! ((ViewGroup) mParent).isViewTransitioning(this)); } void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { if (mGhostView ! = null) { mGhostView.invalidate(true); return; } if (skipInvalidate()) { return; } if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) ! = PFLAG_INVALIDATED || (fullInvalidate && isOpaque() ! = mLastIsOpaque)) { if (fullInvalidate) { mLastIsOpaque = isOpaque(); mPrivateFlags &= ~PFLAG_DRAWN; } mPrivateFlags |= PFLAG_DIRTY; if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if (p ! = null && ai ! = null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); p.invalidateChild(this, damage); } if (mBackground ! = null && mBackground.isProjected()) { final View receiver = getProjectionReceiver(); if (receiver ! = null) { receiver.damageInParent(); } } } } private boolean isProjectionReceiver() { return mBackground ! = null; } private View getProjectionReceiver() { ViewParent p = getParent(); while (p ! = null && p instanceof View) { final View v = (View) p; if (v.isProjectionReceiver()) { return v; } p = p.getParent(); } return null; }Copy the code
  • 1. If the mGhostView is not empty, invoke the invalidate of the mGhostView.

  • 2. Skip this method if the current View is invisible and has no animation, and the parent container is not ViewGroup(probably ViewRootImpl).

  • 3. If PFLAG_DRAWN and PFLAG_HAS_BOUNDS are enabled; Or PFLAG_DRAWING_CACHE_VALID; Or PFLAG_INVALIDATED; Or fullInvalidate is true and the transparency changes, then the following logic is executed:

  • 4. Disable the PFLAG_DRAWN flag. To apply the PFLAG_DIRTY flag to the current View, a new drawing is required. Close the PFLAG_DRAWING_CACHE_VALID.

  • 5. Set the current rect to the position of the four edges of the current View. This means that all views under this position must be redrawn and passed to the child View by invalidateChild for further processing.

  • 6. If the mBackground drawable is set, iterate through the top container to find another parent container that contains the background drawable, and call the parent’s damageInParent method to refresh both.

protected void damageInParent() { if (mParent ! = null && mAttachInfo ! = null) { mParent.onDescendantInvalidated(this, this); }}Copy the code

ViewGroup invalidateChild Refreshes the content of the child layout

The passed child is the current View, and the dirty area is the dirty area where the Layout calculated above has changed.

public final void invalidateChild(View child, final Rect dirty) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo ! = null && attachInfo.mHardwareAccelerated) { onDescendantInvalidated(child, child); return; } ViewParent parent = this; if (attachInfo ! = null) { final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) ! = 0; Matrix childMatrix = child.getMatrix(); final boolean isOpaque = child.isOpaque() && ! drawAnimation && child.getAnimation() == null && childMatrix.isIdentity(); int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY; if (child.mLayerType ! = LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } final int[] location = attachInfo.mInvalidateChildLocation; location[CHILD_LEFT_INDEX] = child.mLeft; location[CHILD_TOP_INDEX] = child.mTop; if (! childMatrix.isIdentity() || (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ! = 0) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); Matrix transformMatrix; if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ! = 0) { Transformation t = attachInfo.mTmpTransformation; boolean transformed = getChildStaticTransformation(child, t); if (transformed) { transformMatrix = attachInfo.mTmpMatrix; transformMatrix.set(t.getMatrix()); if (! childMatrix.isIdentity()) { transformMatrix.preConcat(childMatrix); } } else { transformMatrix = childMatrix; } } else { transformMatrix = childMatrix; } transformMatrix.mapRect(boundingRect); dirty.set((int) Math.floor(boundingRect.left), (int) Math.floor(boundingRect.top), (int) Math.ceil(boundingRect.right), (int) Math.ceil(boundingRect.bottom)); } do { View view = null; if (parent instanceof View) { view = (View) parent; } if (drawAnimation) { if (view ! = null) { view.mPrivateFlags |= PFLAG_DRAW_ANIMATION; } else if (parent instanceof ViewRootImpl) { ((ViewRootImpl) parent).mIsAnimating = true; } } if (view ! = null) { if ((view.mViewFlags & FADING_EDGE_MASK) ! = 0 && view.getSolidColor() == 0) { opaqueFlag = PFLAG_DIRTY; } if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) ! = PFLAG_DIRTY) { view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag; } } parent = parent.invalidateChildInParent(location, dirty); if (view ! = null) { // Account for transform on current parent Matrix m = view.getMatrix(); if (! m.isIdentity()) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); m.mapRect(boundingRect); dirty.set((int) Math.floor(boundingRect.left), (int) Math.floor(boundingRect.top), (int) Math.ceil(boundingRect.right), (int) Math.ceil(boundingRect.bottom)); } } } while (parent ! = null); }}Copy the code
  • 1. Determines whether hardware acceleration is enabled. If so, entrust the event to onDescendantInvalidated.
  • 2. If it is not open, it is software rendering. If you have a child View of the current layout that doesn’t have a unit transformation matrix or you have Flag_support_STATIC_blah turned on. BoundingRect sets the previously calculated dirty region to boundingRect, and traverses the transformation matrix in the grandson View together by matrix calculation. Finally, the boundingRect region is transformed by the transformation matrix to make it larger or smaller. Finally, make boundingRect dirty.
  • 3. Check whether the current ViewParent is a View. Determine if the current View is animating. If it is animating, turn on the PFLAG_DRAW_ANIMATION flag by iterating through the parent layout. MIsAnimating is true if traversing to the top-level ViewRootImpl.
  • 4. If the parent node of the current View is opaque or the transparent part has changed, flag PFLAG_DIRTY to the top parent.
  • 5. Call invalidateChildInParent to process and change the parent container based on the current dirty area.

Here comes the invalidateChildInParent method. Let’s see what this method does.

ViewGroup invalidateChildInParent

public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) { if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) ! = 0) { // either DRAWN, or DRAWING_CACHE_VALID if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) ! = FLAG_OPTIMIZE_INVALIDATE) { dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX, location[CHILD_TOP_INDEX] - mScrollY); if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) { dirty.union(0, 0, mRight - mLeft, mBottom - mTop); } final int left = mLeft; final int top = mTop; if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { if (! dirty.intersect(0, 0, mRight - left, mBottom - top)) { dirty.setEmpty(); } } location[CHILD_LEFT_INDEX] = left; location[CHILD_TOP_INDEX] = top; } else { .... return mParent; } return null; }Copy the code

First check if the PFLAG_DRAWN flag bit and PFLAG_DRAWING_CACHE_VALID are turned on. The PFLAG_DRAWING_CACHE_VALID flag bit is called when updateDisplayListIfDirty is updated in draw, indicating that the draw is based on the result of the last draw. The PFLAG_DRAWN flag bit is turned on with the draw method.

When Android is drawing, it will set the drawing area based on the last drawing result by default.

The principle of drawing cache is as follows: FLAG_OPTIMIZE_INVALIDATE is opened when dispatchDraw dispatches drawing behavior to sub-view process and a LayoutAnimation is set and the delay ratio mDelay is less than 1.

This process is bound to overlap drawing areas, so an optimization is needed. Note, however, that the FLAG_OPTIMIZE_INVALIDATE bit is checked with the FLAG_ANIMATION_DONE bit. NotifyAnimationListener is turned on after the animation completes, as is the default ViewGroup initialization. In this way, the calibration can accurately calculate the drawing area that needs to be updated.

It is divided into the following three steps:

  • 1. Add the offset of sliding X-axis and Y-axis:

The X-axis offset of the dirty zone is location[CHILD_LEFT_INDEX] – mScrollX The Y-axis offset of the dirty zone is location[CHILD_TOP_INDEX] – mScrollY

  • 2. If FLAG_CLIP_CHILDREN is enabled, the clipChild bit is set to determine whether child elements can exceed the parent element’s boundaries. It is generally true and does not allow child elements to exceed parent elements. If it is, you only need to update the intersection between the parent and child elements. If no, set the dirty area to empty.
  • 3. Update the leftmost and topmost values of the Location array.

Remember that a call to invalidateChildInParent during invalidateChild execution is constantly traversed to the top, thus traversing the DecorView as well as the ViewRootImpl.

ViewRootImpl invalidateChild

    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();

        if (dirty == null) {
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }
...

        invalidateRectOnScreen(dirty);

        return null;
    }
Copy the code

The core actually calls invalidateRectOnScreen.

invalidateRectOnScreen
private void invalidateRectOnScreen(Rect dirty) { final Rect localDirty = mDirty; if (! localDirty.isEmpty() && ! localDirty.contains(dirty)) { mAttachInfo.mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); final float appScale = mAttachInfo.mApplicationScale; Final Boolean Intersected = localDirty. Intersect (0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); if (! intersected) { localDirty.setEmpty(); } if (! mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); }}Copy the code

You can see that the dirty region currently passed is recorded in the global variable mDirty. ScheduleTraversals is called directly to draw the View. So what’s the difference with requestLayout? Let’s take a look:

    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
Copy the code

You can see that requestLayout displays the mLayoutRequested flag before executing the scheduleTraversals method.

We extract the core traversals method from FormTraversals:

        final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            mForceNextWindowRelayout = false;
...
        } else {
            maybeHandleWindowMove(frame);
        }

        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout){
....
Copy the code

So you can see that the onMeasure is actually handled in the first if, and the onLayout is handled in didLayou is true. If it is not the first rendering process and the form, Insets, visibility and other factors are not changed, the process will not go to measure. Layout depends on the flag bits set by requestLayout.

The invalidate method normally does not trigger either of these processes directly. Instead, it redraws the region directly. This is why the invalidate update’s newly added View doesn’t render on the Android screen.

This approach is suitable for situations such as LottieView where animations are performed continuously in an area.

View invalidateViewProperty

void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) { if (! isHardwareAccelerated() || ! mRenderNode.isValid() || (mPrivateFlags & PFLAG_DRAW_ANIMATION) ! = 0) { if (invalidateParent) { invalidateParentCaches(); } if (forceRedraw) { mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation } invalidate(false); } else { damageInParent(); }}Copy the code

This method is essentially more concerned with View refresh performance than invalidate. Note that this method is called from the offsetLeftAndRight method. InvalidateViewProperty Both arguments are false, the second is true only for non-hardware-accelerated transformation matrices that are not identity matrices.

Without hardware acceleration, or if the mRenderNode is invalid, the invalidate process will occur. Otherwise, use the “damageInParent” method and delegate the event to “onDescendantInvalidated”.

Let’s take a look at the logic of software rendering:

  • 1. If invalidateParent is true, the invalidateParentCaches method is called to render the parent’s cache
  • 2. If the forceRedraw parameter is true, PFLAG_DRAWN indicates the flag bit. This flag bit determines whether the action of draw is complete and calls the invalidate method. But the argument is false, that is, not based on the results of the last rendering, to find the intersection of the two previous draws.

If the rendering is hardware, hand it to the onDescendantInvalidated method. This method appears multiple times, and it also appears in invalidateChild.

So what do validated Parentcaches and Ondescend1 in validated do?

View invalidateParentCaches

protected void invalidateParentCaches() { if (mParent instanceof View) { ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED; }}Copy the code

In effect, the method is to put a PFLAG_INVALIDATED bit on the parent container.

ViewGroup onDescendantInvalidated

public void onDescendantInvalidated(@NonNull View child, @NonNull View target) { mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION); if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) ! = 0) { mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } if (mLayerType == LAYER_TYPE_SOFTWARE) { mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY; target = this; } if (mParent ! = null) { mParent.onDescendantInvalidated(this, target); }}Copy the code

If PFLAG_DIRTY_MASK is turned on, turn off PFLAG_DIRTY_MASK, and then give the top View PFLAG_DIRTY. Finally, turn off PFLAG_DRAWING_CACHE_VALID.

    static final int PFLAG_DIRTY_MASK                  = 0x00600000;
    static final int PFLAG_DIRTY                       = 0x00200000;
    static final int PFLAG_DIRTY_OPAQUE                = 0x00400000;
Copy the code

You can see if the first two bits of the PFLAG_DIRTY_MASK control are transparent and the second bit is invalid.

Turn off the dirty and opaque flag bits before turning on the DIRTY flag bit. The View area has changed, but not transparently. Call onDraw.

If mLayerType is the LAYER_TYPE_SOFTWARE mode, enable another PFLAG_INVALIDATED flag.

The logic is similar to invalidateParentCaches.

ViewRootImpl onDescendantInvalidated
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) { if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) ! = 0) { mIsAnimating = true; } invalidate(); } void invalidate() { mDirty.set(0, 0, mWidth, mHeight); if (! mWillDrawSoon) { scheduleTraversals(); }}Copy the code

You can see that what’s actually done here is very simple, which is to set the dirty area to global and do a full screen refresh. Although this is global, dirty areas are actually marked from the bottom View to the top View.

What’s the good of that? This will tell you what level of subviews onDraw needs to draw from the top to the bottom. Of course, this mode is specially provided for hardware acceleration, because in hardware acceleration, each RenderNode is the core of rendering, and each RenderNode is from the DisplayLIst View tree of the parent container RenderNode. So you need to start from the parent container and work your way down to find the object that really needs to be rerendered.

So what does PFLAG_INVALIDATED do? As we’ll see in the next article, this essentially tells the hardware to render the object at what level to start the View tree reconstruction.

View invalidateParentIfNeededAndWasQuickRejected

    protected void invalidateParentIfNeededAndWasQuickRejected() {
        if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) != 0) {
            invalidateParentIfNeeded();
        }
    }
Copy the code

Check whether PFLAG2_VIEW_QUICK_REJECTED is enabled. If PFLAG2_VIEW_QUICK_REJECTED is enabled, inflagateparentifneeded is activated. This flag bit is actually turned on almost by the Canvas’s quickReject method, which rejects the current drawing. If yes, check PFLAG2_VIEW_QUICK_REJECTED and return onDraw without calling onDraw.

The time when this method is called is the last time offsetLeftAndTop has changed its offset.

Again, take a look at invalidateParentIfNeeded.

View invalidateParentIfNeeded

protected void invalidateParentIfNeeded() { if (isHardwareAccelerated() && mParent instanceof View) { ((View) mParent).invalidate(true); }}Copy the code

Very simple, in fact, after determining the hardware acceleration, call the invalidate method to draw the View refresh.

Execution conditions of onMeasure

Let’s review the following from the beginning: Whether onMeasure measures is composed of the following factors.

  • 1. First render
  • 2. The window is changed
  • 3. If the Inset area is changed
  • 4. If the top layer of the form’s ViewDecorView is visible, the situation has changed
  • 5. If mWindowAttributes are not empty
  • 6. Updated the Configuration of the entire ViewRootImpl each time, such as horizontal and vertical screen switching, resource theme switching, etc.

As long as the six preconditions are completed, the next step will be judged.

  • 1. The width and height of the original DecorView and WindowFrame have changed
  • 2. The contact mode has changed
    1. ContentInsetsChanged meaning the inset of the content area has changed
  • 4. The Configuration is updated

Only then will the onMeasure be executed.

Execution conditions for onLayout

There are three conditions:

  • 1. RequestLayout calls.
  • 2. After the onMeasure is measured, it indicates that the size of the View has changed and the onLayout needs to be rearranged.

PerformLayout is executed if the View does not Stop or the Draw method is forced to be called.

  • 3. In performLayout, if the ViewGroup determines that the Layout suppression is off, and there is no Layout animation or the animation of the Layout does not change, it will be passed to the child View to execute the Layout process.

The onLayout method is called when these conditions are reached.

The optimization of onLayout

To optimize the logic of onMeasure and onLayout traversal. The onMeasure is cached. If the size of the parent container is determined to be the same, the onMeasure will not traverse the underlying child View for measurement.

But what if the subview changes in the process? Therefore, onMeasure will be carried out in advance during the onLayout process to ensure that the size of each sub-view is normal before placing onLayout.

In this way, similar to the state transition table, which level can be broken traversal can greatly reduce the traversal level of the onMeasure and onLayout of the whole View.

OnLayout executes the process

The ViewRootImpl uses performLayout as the global entry to the View placement process onLayout. In this process:

  • 1. Use the DecorView layout method to create a global View tree.
  • 2. Check whether the onMeasure and onLayout require redrawing and layout. If yes, remeasure and layout these request objects.

The entire core process is in the View layout method. Layout Each time before executing onLayout for the actual layout of each sub-view:

  • 1. The setFrame or setOpticalFrame method will be called to determine whether the current container position is changed because of the parent container. Once the parent container changes, the onLayout traversal of the child View will be called.
  • 2. Check if it is coming from requestLayout, if it is, force an onLayout traversal of the child View.

In the process, the setFrame method does another very important thing, recording the coordinates of the left, right, top, and bottom of the View. Also refresh the View’s renderNode and go back to using the sizeChange method. Call invalidate to send the local refresh drawing command if the View is found to be but, and call invalidateParentCaches to update the dirty bits of the View’s parent layout.

DecorView onLayout

DecorView as the root layout After processing the parent onLayout, it calls methods such as offsetLeftAndRight to offset the layout at the form level. This is based on the size of the four Inset positions set on each form.

FrameLayout onLayout

FrameLayout iterates through all child views once in onLayout, handling gravity. And the layout method of the child View

LinearLayout onLayout

The LinearLayout only iterates through all of its subviews once in onLayout, handling gravity. And the layout method of the child View

RelativeLayout onLayout

Do a single loop through the layout method of each child View.

The role of invalidate

There is no need to traverse the entire View tree onDraw method all the time in the View drawing process. In fact, the onDraw process is the most expensive performance, so Android is optimized through the invalidate command to achieve the ability to partially refresh.

The invalidate workflow is shown below:

What’s the advantage of being a dirty flag bit? The advantage is that you can terminate onDraw by knowing which level you need to traverse, reducing Android drawing time.

In this process, the refresh region relationship between the parent container and the child View is set based on the clipChild flag bit. As follows:

After executing the onLayout method for the entire View tree, the DecorView is called to determine whether to execute the offsetTopAndLeft method. InvalidateChild this method is also used when we need to change the position of a View. This method is used when we need to change the position of a View. This method is used when we need to change the offset of the View’s four edges. This can greatly avoid a lot of traversal loop processing, the schematic diagram is as follows: