The process of customizing View

Custom View is mainly divided into three steps: measure, layout and draw. For direct inheritance of View, only measure and draw are needed. For direct inheritance of view group, three steps are needed: measure, layout and draw. The rendering process of view starts with the performance Traversals method of re-view Root, which calls the performance measure method, the performance Layout method and the performance Draw method in turn to complete the rendering of a view. In the performance measure method, the measure method is called to complete the measurement of itself, and then the onMeasure method is called back. Within the onMeasure method, the measure method of each sub-view is called back to complete the measurement of the sub-view. The sub-view repeats the process and completes the measurement of the whole view tree. The process of performance Layout and performance Draw is similar to that of performance measure, except that dispatchDraw method is called when drawing sub-view. This is the overall process of view rendering. The whole process is known, and we need to know the details of each step before we can write a custom control. Now let's start with the details of measurement.
Measure process:

    /**
     * @param widthMeasureSpec Horizontal space requirements as imposed by the
     *        parent
     * @param heightMeasureSpec Vertical space requirements as imposed by the
     *        parent
     * @see #onMeasure(int, int)
     */
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
	   //Specific Code Ellipsis
        ...
	   onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

First, let's look at the measure ment method of View's source code and see that this method needs to pass two parameters, widthMeasureSpec.
And heightMeasureSpec, through the Parameter annotation of this method, we can know that these two parameters are determined by the parent container.
Because widthMeasureSpec and heightMeasureSpec have the same calculation method and process, for the convenience of explanation, now we have
WidthMeasureSpec is an example. To facilitate understanding the subsequent process, let's first understand what widthMeasureSpec is.
What's that? Simply put, it's the measurement mode. It's a 32-bit int value, the high two represent the measurement mode, and the low 30 represent the test.
Size. The measurement mode and size can be obtained by the following methods
int specMode = MeasureSpec.getMode(measureSpec);//measurement mode
Int specSize = MeasureSpec. getSize (measure Spec); // Measure Size
The android system provides three measurement modes, namely:
MeasureSpec.UNSPECIFIED
This measurement mode indicates that the size of the control is as big as it wants. The parent control has no restrictions on the child control and is generally used within the system.

MeasureSpec.AT_MOST
This measurement pattern indicates that the parent container has specified a specSize, and the maximum width/height of the child control cannot exceed this specSize, but the specific size of view depends on the implementation of view.

MeasureSpec.EXACTLY
This measurement pattern indicates that the parent container has detected the size of the View, which is specSize.
Then look at the onMeasure method

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

Inside this method, it seems very simple, and it just calls the setMeasured Dimension (int measuredWidth, int measuredHeight) method, which sets the width and height of the measurement to view. The input parameters of this method are obtained by getDefaultSize (int size, int measure Spec) method. Now let's see how getDefaultSize (int size, int measure Spec) can obtain the width and height of the measurement.

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

The code inside the getDefaultSize method knows that the measurement size returned by this method is determined by the measurement mode. If the measurement mode is AT_MOST or EXACTLY, the measurement size returned is specSize. If the measurement mode is UNSPECIFIED, the measurement size is the incoming size. So how big is this incoming size? The size of this size is obtained by the getSuggestedMinimumWidth() method. Let's look at the source code of the getSuggestedMinimumWidth() method.

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

As you can see from the code inside the getSuggested MinimumWidth () method, if the backgroundof the view is null, the size returned by this method is mMinWidth, which is actually the size set by the view attribute android:minWidth. If the view does not set the minWidth attribute, the default size of mMinWidth is 0, such as If the view's backgroundis not null, max(mMinWidth, mBackground.getMinimumWidth()) is executed, i.e., the larger values of mMinWidth and mBackground.getMinimumWidth(). Let's look at the mBackground.getMinimumWidth() method, which is a method in the Drawable class. The source code for the getMinimumWidth() method is as follows:

    public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        return intrinsicWidth > 0 ? intrinsicWidth : 0;
    }

By looking at the source code of the getMinimumWidth() method, it is found that if the background is a BitmapDrawable, the size of the BitmapDrawable is returned, and if it is ShapeDrawable, 0 is returned. Through the analysis of the setMeasured Dimension method, we know that the width/height of view measurement is the specSize of the incoming measurSpec measurement mode. Why do we say this? Because the measurement mode is not UNSPECIFIED in our customized view, the case branch of UNSPECIFIED will not be executed in the getDefaultSize method, so the specSize returned by getDefaultSize is specSize. This specSize is the measurement width and height of view, remember
Measuring width and height is not the final width and height, because the final width and height are determined by layout method. This is the measurement process of view. After analyzing this process, some people may wonder how the parent container calculates the measurement mode of view. This situation is only when the view has a parent container. If for DecorView, such a top-level view does not have a parent container, how does its MeasureSpec calculate? Let's first look at how DecorView's MesureSpec is calculated. For DecorView, its implementation class, the measureHierarchy method in ViewRootImpl, has the following code:

   childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth,lp.widht);
   childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight,lp.height);
   performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);

Next, look at the implementation of the getRootMeasureSpec method:

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

From the getRootMeasureSpec method, it can be concluded that there are only two measurement modes of DecorView, one is EXACTLY and the other is AT_MOST. When the layoutParams of DecorView are specific numbers or MATCH_PARENT, its measurement mode is EXACTLY, otherwise it is AT_MOST, and when the width of DecorView is specific. When it comes to numbers, its specSize is the specific number. Otherwise, its specSize is the width or height of the window. In fact, through the getRootMeasureSpec method, we can confirm that DecorView is the first case to go. Its measurement mode is EXACTLY, and specSize is the width or height of windows. After looking at how DecorView's measurement mode is calculated, let's look at how ordinary view's measurement mode is calculated. For normal view, that is, the root view of the layout displayed by our Activity is a ViewGroup, we can look at the measureChildWithMargins() method in the ViewGroup class. The concrete implementation of this method is as follows:

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

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

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

Through the measureChildWithMargins method, we can find that the measureSpec of the child view is calculated before the child view is measured. The specific calculation process is completed in the getChildMeasureSpec() method. Let's look at the implementation of the getChildMeasureSpec() method in the ViewGroup class.

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

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

        int resultSize = 0;
        int resultMode = 0;

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

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

By analyzing the getChildMeasureSpec() code, it is found that the measureSpec of view is determined by the messureSpec of the parent container and the layoutParms of view. And the following conclusions can be drawn:
1. When the width or height of the view is a specific number, such as 100px or 100dp, the spec mode of the view is EXACTLY regardless of the measurement mode of the parent container, and the size of the view is the specific number.

2. When the width or height of view adopts MATCH_PARENT, the spec mode of view is consistent with the measurement mode of parent container. But the value of specSize is different. When the SpecSize of the parent container is UNSPECIFIED, the specSize of the view is 0. When the SpecSize of the parent container is EXACTLY or AT_MOST, the size of the specSize of the view is the remaining space of the parent container.

3. When the width or height of view is WRAP_CONTENT, if the SpecMode of parent container is EXACTLY or AT_MOST, the measurement mode of view is AT_MOST. When the specMode of parent container is UNSPECIFIED, the measurement mode of view is UNSPECIFIED. The measurement size of view is also affected by the measurement mode of parent container, specifically when the specMode of parent container is UNSPECIFIED. When SpecMode is EXACTLY or AT_MOST, the maximum size of specSize of view cannot exceed the remaining space of the parent container. When the specMode of the parent container is UNSPECIFIED, the size of view is 0. According to the logic of getChildMeasureSpec method, we can summarize the measurement mode of view and parent container, and the relationship between view's own layoutParams as follows:

Keywords: Android Attribute Windows

Added by brmcdani on Wed, 28 Aug 2019 12:46:54 +0300