View drawing process in Android, easy to understand

easy to understand

The View class in Android represents the basic building block in the user interface. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class of all widgets. Widgets are components that we usually use to create and interact with users, such as buttons, text input boxes, and so on. The subclass ViewGroup is the base class for all layouts. Layout is an invisible container in which other views or ViewGroups are stacked and their layout properties are set.

All views are managed in a tree structure in the window. You can add a view by code or by editing the xml layout file. View has many subclasses that are responsible for controlling or displaying pictures, text, etc.

Android drawing View

When an Activity starts, it is asked to draw its layout. The Android framework will handle this request, provided, of course, that the Activity provides a reasonable layout. Drawing starts from the root view and traverses the entire view tree from top to bottom. Each ViewGroup is responsible for drawing its sub-Views, and each View is responsible for drawing itself. The drawing process is divided into three steps through the draw() method.

  • Measure
  • Layout
  • Draw

The whole drawing process is carried out in the performTraversals() method in ViewRoot. Part of the source code is as follows.

private void performTraversals() {
    ......
    //最外层的根视图的widthMeasureSpec和heightMeasureSpec由来
    //lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ......
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    ......
    mView.draw(canvas);
    ......
}

Of course, you must know the size and drawing of the view before drawing. So first perform measu and layout (measurement and positioning), as shown in the figure below.

Measure process

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
    //....  
    //回调onMeasure()方法    
    onMeasure(widthMeasureSpec, heightMeasureSpec);  
    //more  
}

Calculate the actual size of the view, and store the height and width in mMeasuredHeight and mMeasureWidth, the two parameters passed in by measure(int, int). MeasureSpec is a 32-bit int value, the upper 2 bits are the measurement mode, and the lower 30 bits are the measurement size. The measurement modes can be divided into the following three types.

  • EXACTLY

Exact value mode, when layout_width or layout_height is specified as a specific value, or match_parent, the system uses EXACTLY.

  • AT_MOST

Maximum value mode, when specified as wrap_content, the size of the control cannot exceed the maximum size allowed by the parent control.

  • UNSPECIFIED

Without specifying the measurement mode, the View can be as big as you want, which is generally not used.

According to the source code above, the measure method cannot be rewritten, and the onMeasure method needs to be rewritten when customizing

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

Viewing the source code shows that the final height and width are set by calling setMeasuredDimension(). If not rewritten, the default is to directly call getDefaultSize to obtain the size.

Use the getMeasuredWidth() and getMeasuredHeight() methods of View to obtain the width and height measured by View. You must ensure that these two methods are called after the onMeasure process to return valid values.

Layout process

The Layout method is used to determine the position of the view layout, as if you know the size of a thing, you must know the position before you can draw it.

mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());

layout gets four parameters, left, top, right, and bottom coordinates, relative to the parent view. As you can see here, the width and height just measured are used.

public void layout(int l, int t, int r, int b) {
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    boolean changed = setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
        .....
        onLayout(changed, l, t, r, b);
        .....
}

Set the coordinates by setFrame. If the coordinates have changed, reposition. If it is a View object, then onLayout is an empty method. Because the positioning is determined by the ViewGroup.

getWidth() and getHeight() will return the correct value after the layout is finished.

A question arises here, what is the difference between getWidth/Height() and getMeasuredWidth/Height()?

  • getWidth(): View the width of the entire View after setting the layout.
  • getMeasuredWidth(): The width occupied by the View content after measuring the content on the View

Draw process

public void draw(Canvas canvas) {
        ......
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */
        // Step 1, draw the background, if needed
        ......
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }
        // skip step 2 & 5 if possible (common case)
        ......
        // Step 2, save the canvas' layers
        ......
            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }
        ......
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);
        // Step 4, draw the children
        dispatchDraw(canvas);
        // Step 5, draw the fade effect and restore layers
        ......
        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, right, top + length, p);
        }
        ......
        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);
        ......
    }

The point is that the third step calls the onDraw method. The other few steps are to draw some corners and corners such as background, scrollBar and the like. Among them, dispatchDraw is used to recursively call the child View, if not, it is not needed. This article mainly analyzes the drawing of Android view. For more in-depth learning or advanced Android development, you can go to "Android Core Architecture Notes" to view detailed learning categories.

Summarize

  • View is the entity of visual UI components in Android.
  • The presentation of View depends on Activity, which is the basic element contained in Activity.
  • View mainly provides methods for component drawing and event handling.
  • View can be divided into container type and entity type.
  • The container type View (ViewGroup) can accommodate other container type Views and entity type Views.
  • Entity type View is mainly used for user interaction, such as: button, text box.

Guess you like

Origin blog.csdn.net/m0_70748845/article/details/132697665