Learning notes

First, Android UI hierarchy drawing system

An Activity in Android exists as a carrier of an application, representing a complete user interface and providing a window for view drawing.

  • In this case, when we’re talking about drawing a View, we’re essentially doing something to a View and its subclasses. View, the top-level parent class of the View control, will be examined in detail in this article. We take the Android UI hierarchy drawing system as the starting point to explore the View.

Figure 1 Hierarchy of views

Android’s UI hierarchy is shown in Figure 1

These things are done in the drawing system
The setContentView method of the PhoneWindow class is called when the Activity setContentView method is called. Window is used to describe the contents and actions displayed in the Window at the top of the Activity view.
The setContentView method of the PhoneWindow class eventually generates a DecorView object (DectorView is an inner PhoneWindow class inherited from FrameLayout).
The root layout contains a FrameLayout layout with a content id. The Activity loads the XML for the layout and then parses the contents of the XML file into a hierarchy of views using LayoutInflaters. Finally, add to the FrameLayout layout with ID as Content.

At this point, the View is finally displayed on the phone screen.

Second, View drawing process analysis

The DecorView is loaded into the Window

Windows Manager plays a key role in loading the DecorView into the Window, and is then handed over to ViewRootImpl for detailed processing. This is illustrated by the following partial ActivityThread source analysis (I’m only showing the core source code here, The detailed source code can be viewed in the code.

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); . R = performResumeActivity(token, clearHide, reason); .if(r.window == null && ! PhoneWindow () {//PhoneWindow () {//PhoneWindow (); // Get the View decor = R.window.getDecorView (); decor.setVisibility(View.INVISIBLE); WindowManager ViewManager wm = a.getwinDowManager (); // Get the ViewManager object, where getWindowManager() is essentially getting the ViewManager subclass WindowManager ViewManager wm = a.getwinDowManager (); .if(r.mPreserveWindow) { ... // Get the ViewRootImpl object. ViewRootImpl impl = decor. GetViewRootImpl (); . }if (a.mVisibleFromClient) {
              if(! a.mWindowAdded) { a.mWindowAdded =true; Wm. addView(decor, l); wm.addView(decor, l); }... }... }Copy the code

Windows Manager adds the DecorView to PhoneWindow, that is, the addView() method executes with the action of adding the view to ViewRoot. ViewRoot is the interface whose implementation class ViewRootImpl implements the addView() method. Finally, the detailed drawing of the view is expanded in performTraversals(), as shown in Figure 2.1 below:

Figure 2.1 Code hierarchy analysis drawn by View

2. The performTraversals() method of ViewRootImpl completes the view drawing process

In the source code ViewRootImpl view specific drawing process is as follows:

private void performTraversals() { // cache mView since it is used so much below... // Final View host = mView; // Assign a value of mAdded to the Step3 member variabletrueTherefore, the condition does not holdif(host == null || ! mAdded)return; // Whether intraversal = is being traversedtrue; // Whether to draw View mWillDrawSoon = immediatelytrue; . Int desiredWindowWidth; // DecorView DecorView requires window width and height int desiredWindowWidth; int desiredWindowHeight; . // In the constructor mFirst is already set totrue, indicating whether the DecorView is drawn for the first timeif (mFirst) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true; // If the window type has a status bar, then the top-level view DecorView requires that the window width and height be the same as the status barif(lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD)  { // NOTE -- system code, won't try to do compat mode. Point size = new Point(); mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else {// Otherwise the width and height of the window required by the top-level DecorView is the width and height of the entire screen mView.getContext().getResources().getDisplayMetrics(); desiredWindowWidth = packageMetrics.widthPixels; desiredWindowHeight = packageMetrics.heightPixels; }}... // Obtain the measurement of the width and height of the view, mWidth and mHeight indicate the width and height of the window, Int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); // Ask host how big it wants to be // performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); . PerformLayout (lp, desiredWindowWidth, desiredWindowHeight); . // performDraw(); }Copy the code

The main process of this method reflects the three main steps of View rendering, which are measurement, placement and drawing. The flow chart is shown in Figure 2.2 below:

Figure 2.2 View drawing process

Next, we complete the specific disassembly analysis of performMeasure(), performLayout() and performDraw(). In essence, we need to locate the onMeasure(), onLayout() and onDraw() methods of the View.

MeasureSpec (MeasureSpec

MeasureSpec (MeasureSpec

First, let’s start with performMeasure(). In the above content, ChildWidthMeasureSpec (); childHeightMeasureSpec (); childWidthMeasureSpec (); childHeightMeasureSpec (); childWidthMeasureSpec ();

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code

Measure (childWidthMeasureSpec, childHeightMeasureSpec); measure(childWidthMeasureSpec, childHeightMeasureSpec)

boolean optical = isLayoutModeOptical(this); if (optical ! = isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int oWidth = insets.left + insets.right; int oHeight = insets.top + insets.bottom; Adjust (widthMeasureSpec, optical?) = MeasureSpec. Adjust (widthMeasureSpec, optical? -oWidth : oWidth); heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); }... if (forceLayout || needsLayout) { // first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, This should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); . }... }Copy the code

Here we should be clear, childWidthMeasureSpec childHeightMeasureSpec MeasureSpec is according to the original wide high specific width calculation for different mode of high value.

MeasureSpec (MeasureSpec

MeasureSpec is the inner class of the View, which encapsulates the size of the View and the width and height of the View. In the Measure process, the system converts the View’s LayoutParams to MeasureSpec based on the rules imposed by the parent container, and then specifies the width and height of the control in the onMeasure() method. Source code and analysis are as follows:

Public static class MeasureSpec {private static final int MODE_SHIFT = 30; public static class MeasureSpec {public static final int MODE_SHIFT = 30; public static class MeasureSpec {public static final int MODE_SHIFT = 30; public static class MeasureSpec {public static final int MODE_SHIFT = 30; public static class MeasureSpec {public static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** @hide */ @IntDef({UNSPECIFIED, EXACTLY, }) @retention (retentionPolicy-.source) public @interface MeasureSpecMode {} UNSPECIFIED, EXACTLY, AT_MOST /** * Measure Specification Mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * Measure specification mode: The parent has determined an exact size *forthe child. The child is going to be given those bounds regardless * of how big it wants to be. */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * Measure specification mode: The child can be as large as it wants up * to the specified size. */ public static final int AT_MOST = 2 << MODE_SHIFT; Public static int makeMeasureSpec(@intrange (from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) {if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        /**
         * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
         * will automatically get a size of 0. Older apps expect this.
         *
         * @hide internal use only for compatibility with system widgets and older apps
         */
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            returnmakeMeasureSpec(size, mode); ** @param measureSpec ** @param measureSpec ** @param measureSpec ** @param measureSpec ** @param measureSpec ** @param measureSpec specification to extract the mode from * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
         *         {@link android.view.View.MeasureSpec#AT_MOST} or
         *         {@link android.view.View.MeasureSpec#EXACTLY}
         */
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return(measureSpec & MODE_MASK); ** @param measureSpec ** @param measureSpec ** @param measureSpec ** @param measureSpec ** @param measureSpec specification to extract the size from * @return the size in pixels defined in the supplied measure specification
         */
        public static int getSize(int measureSpec) {
            return(measureSpec & ~MODE_MASK); } // Check the size mode, width and height of the control. Static int adjust(int measureSpec, int delta) {final int mode = getMode(measureSpec); int size = getSize(measureSpec);if (mode == UNSPECIFIED) {
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }

        /**
         * Returns a String representation of the specified measure
         * specification.
         *
         * @param measureSpec the measure specification to convert to a String
         * @return a String with the following format: "MeasureSpec: MODE SIZE"
         */
        public static String toString(int measureSpec) {
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);

            StringBuilder sb = new StringBuilder("MeasureSpec: ");

            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append("");

            sb.append(size);
            returnsb.toString(); }}Copy the code

MeasureSpec’s constants specify two things, one for the size mode and one for the specific width and height information. The high 2 bits represent the dimension measurement mode, and the low 30 bits represent the specific width and height information.

There are three dimensional measurement modes as follows:

Three types of dimensional measurement modes
The parent container UNSPECIFIED the size of the View. It is generally used for internal measurement of the system
②AT_MOST: maximum mode, corresponding to the XML file specified control size wrap_content attribute, the final size of the child View is the parent View specified size, and the child View size cannot be greater than this value
The parent container measures the exact size of the View that is required by specifying the control as match_parent or a specific value in the XML file

For each View, there is a MeasureSpec that holds the size measurement mode of the View and the specific width and height information. The MeasureSpec is influenced by both its own LayoutParams and the parent container’s MeasureSpec.

Iv. Analysis of the Measure process of View

1. Logic diagram of Measure process of View tree

2. Analysis of the Measure process of View

Then in the analysis in 3.1 above, we can make it clear that in the measure method, the onMeasure() method is finally called to complete the specific measurement of the subview. The source code of onMeasure() method is as follows:

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

The setMeasuredDimension() method is called in onMeasure() to store the width and height of the measuremeasure. failing to do so will trigger an exception in the measuremeasuredimension.

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

Copy the code

The setMeasuredDimension() method passes getDefaultSize(), and then analyzes what was done in getDefaultSize() :

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        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

(MeasureSpec) getDefaultSize () getDefaultSize () getDefaultSize () getDefaultSize If it’s wrapped in a ViewGroup you need to measure it to get its exact value.

3. Problems encountered in the process of View Measure and solutions

The View’s measure process is not synchronized with the Activity’s lifecycle method, so there is no guarantee that a View will be measured when the Activity executes onCreate, onStart, or onResume. If the View has not yet been measured, then the width and height are both 0. Here are three ways to solve this problem:

①Activity/View onWindowsChanged() method

The onWindowFocusChanged() method indicates that the View has been initialized and the width and height are ready to be fetched. This method is called multiple times, both when the Activity continues and when it pauses, with the following code:

public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
       if(hasWindowFocus){ int width=view.getMeasuredWidth(); int height=view.getMeasuredHeight(); }}Copy the code

(2) the post (runnable) method

Post a Runnable to the end of the message queue and wait for Looper to call the Runnable while the View is initialized

@Override  
protected void onStart() {  
    super.onStart();  
    view.post(new Runnable() {  
        @Override  
        public void run() { int width=view.getMeasuredWidth(); int height=view.getMeasuredHeight(); }}); }Copy the code

③ViewTreeObsever uses a number of ViewTreeObserver callback methods to accomplish this function, such as onGlobalLayoutListener interface, The onGlobalLayout method is called back when the state of the View tree changes or the visibility of the View inside the View tree changes. This method will also be called multiple times as the View tree changes.

@Override  
  protected void onStart() {  
    super.onStart();  
    ViewTreeObserver viewTreeObserver=view.getViewTreeObserver();  
    viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {  
        @Override  
        public void onGlobalLayout() { view.getViewTreeObserver().removeOnGlobalLayoutListener(this); int width=view.getMeasuredWidth(); int height=view.getMeasuredHeight(); }}); }Copy the code

Of course, here you can specify the width and height of the child View and the measure mode by using the setMeasureDimension () method.

5. Analysis of the layout process of View

1. Logical diagram of layout of View tree

2. Analysis of the layout process of View

The function of layout is ViewGroup to determine the location of child elements. When the position of ViewGroup is determined, the onLayout will be called in the layout. In the onLayout, it will traverse all the child elements and call the layout method of the child elements.

Set the values of the View variables mLeft, mTop, mRight, and mBottom. These are the four points on the screen that make up the rectangle, which is the position of the View, but in this case the position is relative to the position of the superview. The onLayout method determines the position of all child elements. The ViewGroup sets the position of the child view relative to the parent view by calling the layout function of its children in the onLayout function. The specific position is determined by the layout parameter of the function. Let’s look at the layout method of the View (only the key code is shown) :

/* *@param l view left edge relative to the left edge of the parent layout *@param t view top edge relative to the left edge of the parent layout *@param r view Right edge relative to the left edge of the parent layout *@param b view Public void layout(int l, int t, int r, int b) {... public void layout(int l, int t, int r, int b) {... Int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; / / callsetThe Frame method sets the new values of mLeft, mTop, mBottom, and mRight, Boolean isLayoutModeOptical(mParent) : changed = isLayoutModeOptical(mParent)setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); // Second, if the view position changes, then call the onLayout method to set the child view positionif(changed | | (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) = = PFLAG_LAYOUT_REQUIRED) {/ / call onLayout onLayout (changed, l, t, r, b); . }... }Copy the code

Vi. Analysis of draw process of View

1. Draw flow logic diagram of View tree’s draw

2. Analyze the draw process of View

The comment for the draw() method of the View explains the function of each step in the drawing process. The comment for the draw() method in the source code is as follows, and we will focus on the other steps in the comment except for step 2 and step 5.

/*
         * Draw traversal performs several drawing steps which must be executed
         * inAppropriate order: * * 1. Draw the background * 2. If necessary, save the canvas'Layers to prepare for fading (save canvas background to show gradient effect if needed) * 3. Draw view'S content (Draw the content of the View) * 4. Draw children (Draw the subview) * 5. If necessary, Draw the fading edges and restore layers (If necessary, Draw the gradient edge and restore the canvas layer. * 6. Draw decorations (scrollbarsforInstance) (draw decorations (such as scrollbar)) */Copy the code

DrawBackground () in View

The core source code is as follows:

private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return; }... final int scrollX = mScrollX; final int scrollY = mScrollY;if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else{ canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); }}Copy the code

If the background is offset, essentially the canvas is first offset and then painted on top of it.

(2) Drawing the View content

The source code for drawing the contents of the View is as follows:

protected void onDraw(Canvas canvas) {
    }
Copy the code

This method is an empty implementation, so it is set differently according to the content. Custom views need to override this method and add our own business logic.

(3) Subview drawing

The subview drawing source code is as follows:

    protected void dispatchDraw(Canvas canvas) {

    }
Copy the code

This method is also null, and the ViewGroup iterates over the child View and finally calls the onDraw method of the child View to draw.

④ Decoration drawing

The source code for the decorative drawing is as follows (only the core source code is shown) :

Public void onDrawForeground(Canvas Canvas) {// Draw foreground decoration onDrawScrollIndicators(Canvas); onDrawScrollBars(canvas); . foreground.draw(canvas); }Copy the code

Obviously, the onDrawForeground() method is used here to draw other ornaments, such as the ScrollBar, and display them at the top of the view.

Vii. View redraw

RequestLayout redraws the view

The child View calls the requestLayout method, which will tag the current View and its parent container, and submit it layer by layer until the ViewRootImpl processes the event. The ViewRootImpl will call three processes, starting with measure, Each view and its children are measured, laid out, and drawn.

2. Invalidate redraws the view in the UI thread

When a child View calls invalidate, it will add a token bit to the View and request the parent to renew it. The parent will calculate the area to be redrawn and pass it to the ViewRootImpl, finally triggering performTraversals. Start the View tree redraw process (draw only the views that need to be redrawn).

PostInvalidate redraws the view in a non-UI thread

This method has the same function as the invalidate method, which causes the View tree to redraw, but the conditions are different. PostInvalidate is called from the non-UI thread, while invalidate is called from the UI thread.

  • In general, if a View determines that it no longer fits into the current area, say its LayoutParams have changed and the parent layout needs to remeasure, rearrange, and redraw it, requestLayout is often used. Invalidate is a way to refresh the current View so that it can be redrawn without measurement or layout. Therefore, invalidate is more efficient than requestLayout if the View only needs to be redrawn without measurement.

Refer to the article: link: https://www.jianshu.com/p/af266ff378c6