Lollipop - the drawing process of views in Android

My Rare Earth Nuggets blog releases updates simultaneously:  Lollipop - Drawing Process of Views in Android

I. Introduction

    Activity in Android exists as the carrier of the application. It represents a complete user interface and provides a window to draw various views. When the Activity starts, we will set a content view through the setContentView method. This content view It is the interface that the user sees. There are two forms of View in Android: one is a single View control, and the other is a ViewGroup container that can contain other Views. The previous content views exist in the form of ViewGroups. Look at the hierarchical management diagram of an Android UI management system:

    

    PhoneWindow is the most basic window system in the Android system, and each Activity will create one. PhoneWindow is the interface between Activity and View system interaction. DecorView is essentially a FrameLayout, the ancestor of all Views in the Activity.

Second, the overall process of drawing

    When the application starts, a main Activity is started, and the Android system draws it according to the layout of the Activity. Drawing will start from the performTraversals() method of the root view ViewRoot, and traverse the entire view tree from top to bottom. Each View control is responsible for drawing itself, and VewGroup also needs to be responsible for notifying its own child View to draw. The process of drawing the view can be divided into For three steps, namely measurement (Measure), layout (layout) and drawing (Draw), let's take a look at the key code of performTraversals(), performTraversals() is in the ViewRootImpl class, and the ViewRootImpl class is in \sdk\sources\android Below the -23\android\view package, there is a hidden class, you can't see it in as, directly start the double-click Shift key search, the code:           

  

private void performTraversals() {
                    ...
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                   //Execute the measurement process
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    ...
                    //Execute the layout process
                    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
                    ...      
                    //Execute the drawing process
                    performDraw();
            
    }

三、MeasureSpec

    MeasureSpec represents a 32-bit integer value. Its high 2 bits represent the measurement mode SpecMode, and the low 30 bits represent the regular size SpecSize in a certain measurement mode. MeasureSpec is a static inner class of the View class, which is used to describe how the View should be measured. The code is as follows:

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

    // do not specify measurement mode
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    // precise measurement mode
    public static final int EXACTLY     = 1 << MODE_SHIFT;

    //Maximum measurement mode
    public static final int AT_MOST     = 2 << MODE_SHIFT;

    //Create a MeasureSpec based on the specified size and mode
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

     
 
    // Fine-tune the size of a MeasureSpec
    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);
    }
}

The three measurement modes we need to pay attention to will be used in the later Measure stage

  • UNSPECIFIED: The measurement mode is not specified. The parent view does not limit the size of the subview. The subview can be any size you want. It is usually used inside the system and is rarely used in application development.
  • EXACTLY: precise measurement mode, which takes effect when the layout_width or layout_height of the view is specified as a specific value or match_parent, indicating that the parent view has determined the precise size of the subview. In this mode, the measurement value of the View is the SpecSize value
  • AT_MOST: Maximum value mode, which takes effect when the layout_width or parent_height of the view is specified as wrap_content. At this time, the size of the subview can be any size that does not exceed the maximum size allowed by the parent view.

    For DecorView, its MeasureSpec is determined by the window size and its own LayoutParams; for ordinary View, its MeasureSpec is determined by the parent view's MeasureSpec and its own LayoutParams.

四、Measure

    The Measure operation is used to calculate the actual size of the View. From the previous analysis, the measurement process of the page starts from the performMeasure method. The core code:

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

    The specific measurement operation is distributed to the ViewGroup, which is passed to the child View by the ViewGroup in its measureChild method. The ViewGroup implements the measurement operation by traversing all its sub-Views and calling the measure method of the sub-Views one by one.

// Traverse and measure all Views in the ViewGroup
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        //When the visibility of the View is in the GONE state, it is not measured
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

 

//Measure a specified View
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    //Calculate the MeasureSpec of the child View according to the information such as the MeasureSpec of the parent container and the LayoutParams of the child View
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

    Looking at the measure method of View (ViewGroup), the final measurement is achieved by calling back the onMeasure method, which is usually implemented by a specific subclass of View itself. Developers can also implement custom View by overriding this method.

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...  
}
//If you need to customize the measurement process, subclasses can override this method
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //setMeasuredDimension method is used to set the measured width and height of View
    getDefaultSize (getSuggestedMinimumHeight (), getSuggestedMinimumWidth ());
}

   

//If the View does not override the onMeasure method, the getDefaultSize will be called directly by default to get the width and height of the View
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;
}

Five, layout

    The Layout process is used to determine the layout position of the View in the parent container. After the parent container obtains the position parameters of the child View, it calls the layout method of the child View and transfers the position parameters to the implementation. The performLayout code of ViewRootImpl:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
        ...
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ...          
}

 

public final void layout(int l, int t, int r, int b) {
    
     super.layout(l, t, r, b);
    
}

 

//Empty method, if the subclass is of ViewGroup type, rewrite this method to implement the layout process of all View controls in the ViewGroup
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    
}

 

Six, Draw

    The Draw operation is used to draw the control. The drawing process starts from the performDraw method. The core code:

private void performDraw() {
    draw(fullRedrawNeeded);    
    ...
}
private void draw(boolean fullRedrawNeeded) {
     ...
     if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
            return;
      }
      ...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty) {

        mView.draw(canvas);

             
}

    

       You can see that the draw method of each View is finally called to draw each specific View. The drawing can basically be divided into six steps. The code is as follows:

protected void onDraw(Canvas canvas) {
    //First: draw the background of the View
    drawBackground(canvas);
    ...
    //Second: If necessary, save the canvas layer to prepare for fading
    int saveCount = canvas.getSaveCount();
    ...
    canvas.saveLayer(left, top, right, top + length, null, flags);
    ...
    //Third: draw the content of the View
    onDraw(canvas);
    ...
    //Fourth: draw View's child View
    dispatchDraw(canvas);

    ...
    //Fifth: If necessary, draw the fading edge of the View and restore the layer
    ...
    canvas.restoreToCount(saveCount);

    //Sixth: draw the decoration of the View
    onDrawScrollBars(canvas);

}

7. Open source code base

Finally, I will share a code base that I have accumulated for a long time, only you can't think of it, there is nothing you can't use, welcome to star

https://github.com/xijiufu

Since the github server is in the United States, access is sometimes very slow, and an open-source Chinese address library is also provided, and the codes of the two warehouses are updated synchronously:

http://git.oschina.net/xijiufu

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325468804&siteId=291194637