This article provides an in-depth understanding of how Android View works. It is recommended to read the article for a sample project, which is linked below:

ViewDemo

The source code analyzed in this article is based on Android SDK 29 (Android 10.0, i.e. Android Q).

Due to the word limit of the nuggets article, it is divided into two articles:

Learn more about how Android View works (Part 1)

Learn more about how Android View works

Drawing process

The process starts with the **requestLayout()** method.

// frameworks/base/core/java/android/view/ViewRootImpl.java
@Override
public void requestLayout(a) {
    if(! mHandlingLayoutInLayoutRequest) {Call the checkThread() method
        checkThread();
        mLayoutRequested = true;
        // Call the scheduleTraversals() methodscheduleTraversals(); }}Copy the code

The checkThread() method checks whether the current thread is the same thread that created the ViewRootImpl.

// frameworks/base/core/java/android/view/ViewRootImpl.java
void checkThread(a) {
    if(mThread ! = Thread.currentThread()) {/ / if the current thread is not create ViewRootImpl's thread is thrown CalledFromWrongThreadException anomalies
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views."); }}Copy the code

The scheduleTraversals() method allows the Android system to execute asynchronous messages related to View updates and logic related to View updates first. In the in-depth understanding of Android messaging mechanism and source code analysis (Java layer and Native layer) (top) and in-depth understanding of Android messaging mechanism and source code analysis (Java layer and Native layer) (bottom) have been mentioned in the two articles, the source code is as follows:

// frameworks/base/core/java/android/view/ViewRootImpl.java
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent.View.AttachInfo.Callbacks.ThreadedRenderer.DrawCallbacks {
    // Omit some code
    final Thread mThread;
    // Omit some code

    public ViewRootImpl(Context context, Display display) {
        // Get the current thread
        mThread = Thread.currentThread();
    }

    @UnsupportedAppUsage
    void scheduleTraversals(a) {
        if(! mTraversalScheduled) { mTraversalScheduled =true;
            // Add synchronization barrier messages
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // Execute the add synchronization barrier message
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    // Omit some code

    // Create TraversalRunnable object
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    // Omit some code
}
Copy the code

TraversalRunnable class TraversalRunnable class TraversalRunnable class TraversalRunnable class TraversalRunnable class TraversalRunnable class TraversalRunnable class TraversalRunnable class

// frameworks/base/core/java/android/view/ViewRootImpl.java
final class TraversalRunnable implements Runnable {
    @Override
    public void run(a) {
        // Invoke doTraversal()doTraversal(); }}Copy the code

The **doTraversal() traversal method:

// frameworks/base/core/java/android/view/ViewRootImpl.java
void doTraversal(a) {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // Delete the synchronization barrier message
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        // Call the performTraversals() method
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false; }}}Copy the code

**performTraversals() traversals ()

// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performTraversals(a) {
    // Omit some code

    if(mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params ! =null || mForceNextWindowRelayout) {
        // Omit some code

        if(! mStopped || mReportNextDraw) {booleanfocusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) ! =0);
            if(focusChangedDueToTouchMode || mWidth ! = host.getMeasuredWidth() || mHeight ! = host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
                        + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                        + " mHeight=" + mHeight
                        + " measuredHeight=" + host.getMeasuredHeight()
                        + " coveredInsetsChanged=" + contentInsetsChanged);

                // Call performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) and pass in the root view's width and height MeasureSpec, respectively
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                / / implementation WindowManager. LayoutParams of weight, according to the need to increase the size, and when needed to measure
                int width = host.getMeasuredWidth();
                int height = host.getMeasuredHeight();
                boolean measureAgain = false;

                if (lp.horizontalWeight > 0.0 f) {
                    width += (int) ((mWidth - width) * lp.horizontalWeight);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                            MeasureSpec.EXACTLY);
                    measureAgain = true;
                }
                if (lp.verticalWeight > 0.0 f) {
                    height += (int) ((mHeight - height) * lp.verticalWeight);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                            MeasureSpec.EXACTLY);
                    measureAgain = true;
                }

                if (measureAgain) {
                    if (DEBUG_LAYOUT) Log.v(mTag,
                            "And hey let's measure once more: width=" + width
                                    + " height=" + height);
                    // Call performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) again, The root view's width and height MeasureSpec are passed in, respectively
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                }

                layoutRequested = true; }}}else {
        // Omit some code
    }

    // Omit some code

    final booleandidLayout = layoutRequested && (! mStopped || mReportNextDraw);boolean triggerGlobalLayoutListener = didLayout
            || mAttachInfo.mRecomputeGlobalAttributes;
    if (didLayout) {
        / / call performLayout (WindowManager. LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) method
        performLayout(lp, mWidth, mHeight);

        // Omit some code
    }

    // Omit some code

    booleancancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || ! isViewVisible;if(! cancelDraw) {// Omit some code

        // Call performDraw()
        performDraw();
    } else {
        if (isViewVisible) {
            // If draw is cancelled and the View is visible, the scheduleTraversals() method is called again
            scheduleTraversals();
        } else if(mPendingTransitions ! =null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations(); } mPendingTransitions.clear(); }}// Omit some code
}
Copy the code

**performMeasure(int childWidthMeasureSpec, Int childHeightMeasureSpec) method, the * * * * performLayout (WindowManager. LayoutParams lp, int desiredWindowWidth, Int desiredWindowHeight) method and performDraw()** method start.

Measure the process

MeasureSpec is static inner class of the View class, it represents a 32-bit int value, high two representative SpecMode (measurement mode), low 30 representative SpecSize (specification size) in a measurement mode, the source code is as follows:

// View.java
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 {}

    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    public static final int EXACTLY     = 1 << MODE_SHIFT;

    public static final int AT_MOST     = 2 << MODE_SHIFT;

    // Omit some code
}
Copy the code

SpecMode is divided into three modes, as follows:

  • UNSPECIFIED: The parent element has no constraints added to the child elements, which can be of any size. This mode is typically used internally.
  • EXACTLY: The parent has already determined the exact size of the child, i.e. the final size of the child is determined by the value of SpecSize, corresponding to the match_parent and concrete value modes in LayoutParams.
  • AT_MOST: The size of the child element cannot be greater than the value of the SpecSize of the parent element. The default size of the child element corresponds to wrAP_content in LayoutParams.

A View’s MeasureSpec is determined by the parent’s MeasureSpec and its own LayoutParams.

Measure process starts with ViewRootImpl **performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)**.

// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        // Call the View's measure(int widthMeasureSpec, int heightMeasureSpec) method
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code

View **measure(int widthMeasureSpec, int heightMeasureSpec)**

// View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    // Omit some code

    if (forceLayout || needsLayout) {
        // Omit some code
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // Call onMeasure(int widthMeasureSpec, int heightMeasureSpec)
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            // Omit some code
        }

        // Omit some code
    }

    // Omit some code
}
Copy the code

**onMeasure(int widthMeasureSpec, int heightMeasureSpec)

// View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Copy the code

Developers can rewrite this method to change the logic of measuring views.

Look at the first * * getSuggestedMinimumWidth () method and getSuggestedMinimumHeight () method, * * source as shown below:

// View.java
protected int getSuggestedMinimumHeight(a) {
    return (mBackground == null)? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }protected int getSuggestedMinimumWidth(a) {
    return (mBackground == null)? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }Copy the code

The getSuggestedMinimumWidth() method returns the View’s suggested minimum width, and minWidth is returned if the View has no background, and the minimum width of the background otherwise.

GetSuggestedMinimumHeight () method returns the is suggested that the minimum height of the View, if there is no background, the View is returned minHeight, otherwise it returns the minimum height of the background.

**getDefaultSize(int size, int measureSpec)**

// View.java
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    // Get the measurement mode
    int specMode = MeasureSpec.getMode(measureSpec);
    // Get the size in this measurement mode
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}
Copy the code

**setMeasuredDimension(int measuredWidth, int measuredHeight)

// View.java
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;

        // Get the measurement width of the View
        measuredWidth  += optical ? opticalWidth  : -opticalWidth;
        // Get the measured height of the View
        measuredHeight += optical ? opticalHeight : -opticalHeight;
    }
    // Call the setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) method
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
Copy the code

**setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)**

// View.java
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
Copy the code

The View’s getMeasuredWidth() method uses the value of the member variable mMeasuredWidth, which returns the original measurement width, and the View’s getMeasuredHeight() method uses the value of the member variable mMeasuredWidth, which returns the original measurement width. Its function is to return the original measurement height, the source code is as follows:

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

public final int getMeasuredHeight(a) {
    return mMeasuredHeight & MEASURED_SIZE_MASK;
}
Copy the code

So if we look at the measure process of a View, let’s look at the measure process of a ViewGroup, **measureChildren(int widthMeasureSpec, int heightMeasureSpec)** measureChildren(int widthMeasureSpec, int heightMeasureSpec)

// View.java
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    // Execute the loop
    for (int i = 0; i < size; ++i) {
        // Get the children of the ViewGroup
        final View child = children[i];
        if((child.mViewFlags & VISIBILITY_MASK) ! = GONE) {// If the child is VISIBLE or INVISIBLE, measure the child. Call the measureChild(View Child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) methodmeasureChild(child, widthMeasureSpec, heightMeasureSpec); }}}Copy the code

MeasureChild (View Child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)** measureChild(View Child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)

// View.java
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    // Call the View's measure(int widthMeasureSpec, int heightMeasureSpec) method
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code

example

LinearLayout (int widthMeasureSpec, int heightMeasureSpec)** LinearLayout (int widthMeasureSpec, int heightMeasureSpec)**

// LinearLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        // If it is vertical, call measureVertical(int widthMeasureSpec, int heightMeasureSpec)
        measureVertical(widthMeasureSpec, heightMeasureSpec);
    } else {
        // If horizontal, call measureHorizontal(int widthMeasureSpec, int heightMeasureSpec)measureHorizontal(widthMeasureSpec, heightMeasureSpec); }}Copy the code

**measureVertical(int widthMeasureSpec, int heightMeasureSpec)** measureVertical(int widthMeasureSpec, int heightMeasureSpec)** measureVertical(int widthMeasureSpec, int heightMeasureSpec)

// LinearLayout.java
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    // mTotalLength is the height of all the children plus paddingTop and paddingBottom. Note that this is different from the height of the LinearLayout itself
    mTotalLength = 0;
    // The maximum width of all child elements
    int maxWidth = 0;
    int childState = 0;
    // The maximum width of any child element whose layout_weight attribute is less than or equal to 0
    int alternativeMaxWidth = 0;
    // The maximum width of any child element whose layout_weight attribute has a value greater than 0
    int weightedMaxWidth = 0;
    boolean allFillParent = true;
    // The sum of the weights of all child elements
    float totalWeight = 0;

    final int count = getVirtualChildCount();

    // Get the width measurement mode
    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    // Get the height measurement mode
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    boolean matchWidth = false;
    boolean skippedMeasure = false;

    final int baselineChildIndex = mBaselineAlignedChildIndex;
    final boolean useLargestChild = mUseLargestChild;

    int largestChildHeight = Integer.MIN_VALUE;
    int consumedExcessSpace = 0;

    int nonSkippedChildCount = 0;

    // Execute the loop
    for (int i = 0; i < count; ++i) {
        // Get the child element
        final View child = getVirtualChildAt(i);
        if (child == null) {
            mTotalLength += measureNullChild(i);
            continue;
        }

        if (child.getVisibility() == View.GONE) {
           i += getChildrenSkipCount(child, i);
           continue;
        }

        nonSkippedChildCount++;
        if (hasDividerBeforeChildAt(i)) {
            mTotalLength += mDividerHeight;
        }

        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        totalWeight += lp.weight;

        final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
        if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
            final int totalLength = mTotalLength;
            mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
            skippedMeasure = true;
        } else {
            // Omit some code
            final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
            MeasureChildBeforeLayout (View Child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, Int totalHeight) method
            measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                    heightMeasureSpec, usedHeight);

            // Omit some code
        }

        // Omit some code
    }

    // Omit some code

    // mTotalLength plus paddingTop and paddingBottom
    mTotalLength += mPaddingTop + mPaddingBottom;

    int heightSize = mTotalLength;

    // Take the maximum height and the recommended minimum height
    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

    // Omit some code

    if(! allFillParent && widthMode ! = MeasureSpec.EXACTLY) { maxWidth = alternativeMaxWidth; }// maxWidth plus paddingLeft and paddingRight
    maxWidth += mPaddingLeft + mPaddingRight;

    // Take the maximum width and recommended minimum width
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    // Call setMeasuredDimension(int measuredWidth, int measuredHeight)
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            heightSizeAndState);

    if(matchWidth) { forceUniformWidth(count, heightMeasureSpec); }}Copy the code

measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, Int totalHeight) is used to measure the subelements of the LinearLayout, and the system uses the member variable mTotalLength to store the initial vertical height of the LinearLayout. Both make the member variable mTotalLength plus the height of the child element and the margin and padding properties of the child element in the vertical direction.

Layout process

Layout process from ViewRootImpl class * * performLayout (WindowManager. LayoutParams lp, int desiredWindowWidth, Int desiredWindowHeight)** method start, source code as shown below:

// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                           int desiredWindowHeight) {
    // Omit some code

    final View host = mView;
    if (host == null) {
        return;
    }
    if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
        Log.v(mTag, "Laying out " + host + " to (" +
                host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
    }

    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try {
        // Call View layout(int L, int t, int r, int b)
        host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight());

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

Call layout(int L, int T, int r, int B) and pass in 0 for left position, 0 for top position, 0 for right position, and pass in height for bottom position, source code as follows:

// View.java
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
    // Omit some code

    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 (Boolean changed, int left, int top, int right, int bottom
        onLayout(changed, l, t, r, b);

        // Omit some code
    }

    // Omit some code
}
Copy the code

The isLayoutModeOptical(Object O) method returns true if it is passed a ViewGroup that uses an optical boundary layout, and false otherwise.

// View.java
public static boolean isLayoutModeOptical(Object o) {
    return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
Copy the code

**setOpticalFrame(int left, int top, int right, int bottom)**

// View.java
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
    Insets parentInsets = mParent instanceof View ?
            ((View) mParent).getOpticalInsets() : Insets.NONE;
    Insets childInsets = getOpticalInsets();
    Call setFrame(int left, int top, int right, int bottom)
    return setFrame(
            left   + parentInsets.left - childInsets.left,
            top    + parentInsets.top  - childInsets.top,
            right  + parentInsets.left + childInsets.right,
            bottom + parentInsets.top  + childInsets.bottom);
}
Copy the code

SetFrame (int left, int top, int right, int bottom); setFrame(int left, int top, int right, int bottom);

// View.java
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    if (DBG) {
        Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                + right + "," + bottom + ")");
    }

    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;
        booleansizeChanged = (newWidth ! = oldWidth) || (newHeight ! = oldHeight);// Invalidate the old position
        invalidate(sizeChanged);

        // Assign left to the member variable mLeft
        mLeft = left;
        // Assign top to the member variable mTop
        mTop = top;
        // Assign right to the member variable mRight
        mRight = right;
        // Assign bottom to the member variable mBottom
        mBottom = bottom;
        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

        mPrivateFlags |= PFLAG_HAS_BOUNDS;


        // Omit some code
    }
    return changed;
}
Copy the code

View getWidth() value is to use the mRight value minus the mLeft value, View getHeight() value is to use the mBottom value minus the mTop value, source code:

// View.java
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth(a) {
    return mRight - mLeft;
}

@ViewDebug.ExportedProperty(category = "layout")
public final int getHeight(a) {
    return mBottom - mTop;
}
Copy the code

**onLayout(Boolean changed, int left, int top, int right, int bottom)

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

Developers can override this method to change the layout View logic.

example

LinearLayout (Boolean changed, int L, int T, int R, int B)**

// LinearLayout.java
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (mOrientation == VERTICAL) {
        // Call layoutVertical(int left, int top, int right, int bottom) if it is vertical
        layoutVertical(l, t, r, b);
    } else {
        // Call layoutHorizontal(int left, int top, int right, int bottom) if it is verticallayoutHorizontal(l, t, r, b); }}Copy the code

**layoutVertical(int left, int top, int right, int bottom)** layoutVertical(int left, int top, int right, int bottom)

// LinearLayout.java
void layoutVertical(int left, int top, int right, int bottom) {
    final int paddingLeft = mPaddingLeft;

    int childTop;
    int childLeft;

    // The default width of the parent element
    final int width = right - left;
    // The child element defaults to the value of right
    int childRight = width - mPaddingRight;

    // The space available for child elements
    int childSpace = width - paddingLeft - mPaddingRight;

    // Number of child elements
    final int count = getVirtualChildCount();

    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
    final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

    // Set the value of top for the first child element based on the gravity property set
    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;
    }

    // Execute the loop
    for (int i = 0; i < count; i++) {
        // Get the child element
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if(child.getVisibility() ! = GONE) {// Get the measured width of the child element
            final int childWidth = child.getMeasuredWidth();
            // Get the measured height of the child element
            final int childHeight = child.getMeasuredHeight();

            // Get the child's LayoutParams
            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);
            // Set the left value according to the gravity property of the child element
            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)) {
                // Add the height of the divider if there is one
                childTop += mDividerHeight;
            }

            // The value of top plus the value of marginTop
            childTop += lp.topMargin;
            // Call setChildFrame(View Child, int left, int top, int width, int height) to set the layout position of the child in the parent elementsetChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); }}}Copy the code

SetChildFrame (View Child, int left, int top, int width, int height) setChildFrame(View Child, int left, int top, int width, int height)

// LinearLayout.java
private void setChildFrame(View child, int left, int top, int width, int height) {
    child.layout(left, top, left + width, top + height);
}
Copy the code

This method calls the layout(int L, int T, int R, int B) methods of the child element to execute the Layout process.

The draw process

The draw process starts with the **performDraw()** method of the ViewRootImpl class.

// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw(a) {
    // Omit some code

    try {
        // Call draw(Canvas Canvas)
        boolean canUseAsync = draw(fullRedrawNeeded);
        if(usingAsyncReport && ! canUseAsync) { mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
            usingAsyncReport = false; }}finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }

    // Omit some code
}
Copy the code

**draw(Canvas Canvas)**

// frameworks/base/core/java/android/view/ViewRootImpl.java
@CallSuper
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    int saveCount;

    // Step 1: Paint the background if necessary
    drawBackground(canvas);

    // Generally, steps 2 and 5 will be skipped if possible
    final int viewFlags = mViewFlags;
    booleanhorizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! =0;
    booleanverticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! =0;
    if(! verticalEdges && ! horizontalEdges) {// Step 3: Draw the View contents
        onDraw(canvas);

        // Step 4: Draw child elements
        dispatchDraw(canvas);

        drawAutofilledHighlight(canvas);

        An overlay is a part of the content that is drawn below the foreground
        if(mOverlay ! =null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        // Step 6: Draw decorations (foreground, scrollbar)
        onDrawForeground(canvas);

        // Step 7: Draw default focus highlights
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }

        // The entire drawing process is completed
        return;
    }

    boolean drawTop = false;
    boolean drawBottom = false;
    boolean drawLeft = false;
    boolean drawRight = false;

    float topFadeStrength = 0.0 f;
    float bottomFadeStrength = 0.0 f;
    float leftFadeStrength = 0.0 f;
    float rightFadeStrength = 0.0 f;

    // Step 2: Save the canvas layers if necessary and prepare to fade (fading)
    int paddingLeft = mPaddingLeft;

    final boolean offsetRequired = isPaddingOffsetRequired();
    if (offsetRequired) {
        paddingLeft += getLeftPaddingOffset();
    }

    int left = mScrollX + paddingLeft;
    int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
    int top = mScrollY + getFadeTop(offsetRequired);
    int bottom = top + getFadeHeight(offsetRequired);

    if (offsetRequired) {
        right += getRightPaddingOffset();
        bottom += getBottomPaddingOffset();
    }

    final ScrollabilityCache scrollabilityCache = mScrollCache;
    final float fadeHeight = scrollabilityCache.fadingEdgeLength;
    int length = (int) fadeHeight;

    // Clip fade length, if the top and bottom fade overlap will produce strange artifacts
    if (verticalEdges && (top + length > bottom - length)) {
        length = (bottom - top) / 2;
    }

    // You can also clip the horizontal gradient if necessary
    if (horizontalEdges && (left + length > right - length)) {
        length = (right - left) / 2;
    }

    if (verticalEdges) {
        topFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getTopFadingEdgeStrength()));
        drawTop = topFadeStrength * fadeHeight > 1.0 f;
        bottomFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getBottomFadingEdgeStrength()));
        drawBottom = bottomFadeStrength * fadeHeight > 1.0 f;
    }

    if (horizontalEdges) {
        leftFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getLeftFadingEdgeStrength()));
        drawLeft = leftFadeStrength * fadeHeight > 1.0 f;
        rightFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getRightFadingEdgeStrength()));
        drawRight = rightFadeStrength * fadeHeight > 1.0 f;
    }

    saveCount = canvas.getSaveCount();
    int topSaveCount = -1;
    int bottomSaveCount = -1;
    int leftSaveCount = -1;
    int rightSaveCount = -1;

    int solidColor = getSolidColor();
    if (solidColor == 0) {
        if (drawTop) {
            topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length);
        }

        if (drawBottom) {
            bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
        }

        if (drawLeft) {
            leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom);
        }

        if(drawRight) { rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom); }}else {
        scrollabilityCache.setFadeColor(solidColor);
    }

    // Step 3: Draw the View contents
    onDraw(canvas);

    // Step 4: Draw child elements
    dispatchDraw(canvas);

    // Step 5: If necessary, paint faded edges and restore layers
    final Paint p = scrollabilityCache.paint;
    final Matrix matrix = scrollabilityCache.matrix;
    final Shader fade = scrollabilityCache.shader;

    // The restoration must be done in the order in which it was saved
    if (drawRight) {
        matrix.setScale(1, fadeHeight * rightFadeStrength);
        matrix.postRotate(90);
        matrix.postTranslate(right, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(rightSaveCount, p);

        } else{ canvas.drawRect(right - length, top, right, bottom, p); }}if (drawLeft) {
        matrix.setScale(1, fadeHeight * leftFadeStrength);
        matrix.postRotate(-90);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(leftSaveCount, p);
        } else{ canvas.drawRect(left, top, left + length, bottom, p); }}if (drawBottom) {
        matrix.setScale(1, fadeHeight * bottomFadeStrength);
        matrix.postRotate(180);
        matrix.postTranslate(left, bottom);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(bottomSaveCount, p);
        } else{ canvas.drawRect(left, bottom - length, right, bottom, p); }}if (drawTop) {
        matrix.setScale(1, fadeHeight * topFadeStrength);
        matrix.postTranslate(left, top);
        fade.setLocalMatrix(matrix);
        p.setShader(fade);
        if (solidColor == 0) {
            canvas.restoreUnclippedLayer(topSaveCount, p);
        } else {
            canvas.drawRect(left, top, right, top + length, p);
        }
    }

    canvas.restoreToCount(saveCount);

    drawAutofilledHighlight(canvas);

    An overlay is a part of the content that is drawn below the foreground
    if(mOverlay ! =null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
    }

    // Step 6: Draw decorations (foreground and scrollbar)
    onDrawForeground(canvas);

    if(debugDraw()) { debugDrawFocus(canvas); }}Copy the code

View **dispatchDraw(Canvas Canvas)**

// View.java
protected void dispatchDraw(Canvas canvas) {}Copy the code

ViewGroup () {ViewGroup () {ViewGroup () {ViewGroup () {ViewGroup () {ViewGroup () {ViewGroup ();

// ViewGroup.java
@Override
protected void dispatchDraw(Canvas canvas) {
    boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
    // Get the number of child elements
    final int childrenCount = mChildrenCount;
    // Get an array of child elements, which is the View array
    final View[] children = mChildren;
    int flags = mGroupFlags;

    if((flags & FLAG_RUN_ANIMATION) ! =0 && canAnimate()) {
        final booleanbuildCache = ! isHardwareAccelerated();// Execute the loop
        for (int i = 0; i < childrenCount; i++) {
            // Get the child element
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                // If the View is visible, animate it
                finalLayoutParams params = child.getLayoutParams(); attachLayoutAnimationParameters(child, params, i, childrenCount); bindLayoutAnimation(child); }}final LayoutAnimationController controller = mLayoutAnimationController;
        if (controller.willOverlap()) {
            mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
        }

        controller.start();

        mGroupFlags &= ~FLAG_RUN_ANIMATION;
        mGroupFlags &= ~FLAG_ANIMATION_DONE;

        if(mAnimationListener ! =null) { mAnimationListener.onAnimationStart(controller.getAnimation()); }}// Omit some code
    final ArrayList<View> preorderedList = usingRenderNodeProperties
            ? null : buildOrderedChildList();
    final boolean customOrder = preorderedList == null
            && isChildrenDrawingOrderEnabled();
    for (int i = 0; i < childrenCount; i++) {
        // Omit some code
        if((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! =null) {
            Call drawChild(Canvas Canvas, View Child, long drawingTime)more |= drawChild(canvas, child, drawingTime); }}// Omit some code
}
Copy the code

**drawChild(Canvas, View child, long drawingTime)**

// ViewGroup.java
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}
Copy the code

This method calls View’s **draw(Canvas Canvas)** method.

To summarize, the DRAW process is divided into the following seven steps:

  1. Draw the background.
  2. If necessary, save the layers of the canvas and prepare to fade.
  3. Draw the contents of the View.
  4. Let me draw the child element.
  5. If necessary, paint the fading edges and restore layers.
  6. Draw decorations (foreground and scroll bar).
  7. Draw default focus highlighting.

example

Finally look at the next example: LinearLayout, which rewrites the **onDraw(Canvas Canvas)** method, source code as follows:

// LinearLayout.java
@Override
protected void onDraw(Canvas canvas) {
    if (mDivider == null) {
        // If there is no divider, end the method
        return;
    }

    if (mOrientation == VERTICAL) {
        // If it is vertical, call the drawDividersVertical(Canvas Canvas) method
        drawDividersVertical(canvas);
    } else {
        // If it is horizontal, call the drawDividersHorizontal(Canvas Canvas) methoddrawDividersHorizontal(canvas); }}Copy the code

In this case, look at the ** dividersvertical (Canvas Canvas)** method, the source code is as follows:

// LinearLayout.java
void drawDividersVertical(Canvas canvas) {
    // Get the virtual number of child elements
    final int count = getVirtualChildCount();
    // Execute the loop
    for (int i = 0; i < count; i++) {
        // Get the child element
        final View child = getVirtualChildAt(i);
        if(child ! =null&& child.getVisibility() ! = GONE) {if (hasDividerBeforeChildAt(i)) {
                // If the child is VISIBLE or INVISIBLE and the View has a partition line, draw a horizontal partition line
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final inttop = child.getTop() - lp.topMargin - mDividerHeight; drawHorizontalDivider(canvas, top); }}}if (hasDividerBeforeChildAt(count)) {
        // Get the last View that is not VISIBLE or INVISIBLE
        final View child = getLastNonGoneChild();
        int bottom = 0;
        if (child == null) {
            bottom = getHeight() - getPaddingBottom() - mDividerHeight;
        } else {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            bottom = child.getBottom() + lp.bottomMargin;
        }
        // Draw a horizontal dividing linedrawHorizontalDivider(canvas, bottom); }}Copy the code

What this method does is draw horizontal dividing lines.

Issue with updating UI in child thread

Take a look at the first example, which looks like this:

package com.tanjiajun.viewdemo

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

/** * Created by TanJiaJun on 2020/10/8. */
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Create a child thread and start it
        Thread {
            // Update the UI in the child thread and set the text of the TextView with id tv_content to Tan Jiajun
            findViewById<TextView>(R.id.tv_content).text = "Tan Ka Chun"
        }.start()
    }

}
Copy the code

This code is updating the UI in the child thread, and the results execute smoothly and as expected.

I modify the first example to get the second example, which looks like this:

package com.tanjiajun.viewdemo

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

/** * Created by TanJiaJun on 2020/10/8. */
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Create a child thread and start it
        Thread {
            // Let the child thread sleep for a second
            Thread.sleep(1000)
            // Update the UI in the child thread and set the text of the TextView with id tv_content to Tan Jiajun
            findViewById<TextView>(R.id.tv_content).text = "Tan Ka Chun"
        }.start()
    }

}
Copy the code

This code also updates the UI on the child thread and puts the child thread to sleep for a second, raising the following exception:

The 2020-10-08 17:02:25. 544, 8619-8665 / com. Tanjiajun. Viewdemo E/AndroidRuntime: FATAL EXCEPTION: Thread - 2 Process: com.tanjiajun.viewdemo, PID: 8619 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753) at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225) at android.view.View.requestLayout(View.java:23093) at android.view.View.requestLayout(View.java:23093) at android.view.View.requestLayout(View.java:23093) at android.view.View.requestLayout(View.java:23093) at android.view.View.requestLayout(View.java:23093) at android.view.View.requestLayout(View.java:23093) at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172) at android.view.View.requestLayout(View.java:23093) at android.widget.TextView.checkForRelayout(TextView.java:8908) at android.widget.TextView.setText(TextView.java:5730) at android.widget.TextView.setText(TextView.java:5571) at android.widget.TextView.setText(TextView.java:5528) at com.tanjiajun.viewdemo.MainActivity$onCreate$1.run(MainActivity.kt:20) at java.lang.Thread.run(Thread.java:764)Copy the code

Throws CalledFromWrongThreadException is unusual, in the previous explanation checkThread also mentioned () method, this method is to examine the effect of the current thread is created ViewRootImpl thread, the View will be informed if it is executed drawing process, Otherwise, throw CalledFromWrongThreadException abnormalities, sample code ViewRootImpl is created in the main thread, which is judge whether to give priority to the thread, First example didn’t sell them CalledFromWrongThreadException is the cause of abnormal, because call setText (CharSequence text) method didn’t create ViewRootImpl when, The View drawing process is executed after the Activity’s onResume method. The ViewRootImpl is created after the onResume method, so the checkThread() method has not yet been called. This time so notify the UI refresh wouldn’t throw CalledFromWrongThreadException abnormalities, the second example thrown CalledFromWrongThreadException is the cause of abnormal, because let the thread to sleep for a second, The onResume method has already been executed and ViewRootImpl has been created on the main thread. The checkThread() method will be called after setText(CharSequence text) is called to tell the UI to refresh, and the current thread is a child thread. And the main thread is not the same thread, so throw CalledFromWrongThreadException anomalies.

Google actually says this:

The Android UI toolkit is not thread-safe.

Google is saying that the Android UI Toolkit is not thread-safe, and Google is not saying that UI updates are not allowed in worker threads (not the main thread).

digression

Android open Source project (AOSP) code search tool

Android Open Source Project (AOSP) code search tool

My GitHub: TanJiaJunBeyond

Common Android Framework: Common Android framework

My nuggets: Tan Jiajun

My simple book: Tan Jiajun

My CSDN: Tan Jiajun