Introduction:

Controls are an essential part of every Android App, whether you use system controls or custom controls.

The main content

  • Android Control Architecture
  • View measurement and rendering
  • ViewGroup measurement and drawing

The specific content

Understand Android control architecture and View and ViewGroup measurement drawing, convenient to customize the control.

Android Control Architecture

In Android, each control occupies a rectangular area of the interface. In Android, controls are roughly divided into two categories: ViewGroup controls and View controls. A ViewGroup control can contain multiple View controls as a parent control and manage the View controls it contains. Through ViewGroup, the controls on the whole interface form a tree structure, which is often referred to as the control tree. The upper control is responsible for the measurement and drawing of the lower control, and the transfer of interactive events. The findViewById() method is typically used in an Activity to find the corresponding element in a tree of controls through a tree-depth first traversal. At the top of each control tree, there is a ViewParent object, which is the control core of the whole tree. All interaction management events are scheduled and assigned by this object, so that the whole view can be controlled as a whole. The following figure shows a View tree.

Normally, the setContentView() method is used in an Activity to set up a layout, and the layout content is not actually displayed until the method is called. The architecture of the Android interface is shown below.

In each Activity, there is a Window object, which in Android is usually implemented by PhoneWindow. PhoneWindow sets a DecorView as the root View of the entire application window. A DecorView, as the top-level view of a window interface, encapsulates common methods that encapsulate window operations. All View listener events are received through WindowManagerService and the corresponding onClickListener is called back and forth through the Activity object. On display it splits the screen into two parts, a TitleView and a ContentView. As shown in the figure below.

The second layer of the view tree is loaded with a LinearLayout, which is used as a ViewGroup. The layout structure of this layer will set different layouts according to the corresponding parameters, such as the most commonly used layout, which shows TitleBar above and Content below. The user can set requestWindowFeature(window.feature_no_title) to enable full screen display, and the layout in the view tree is Content only. This explains why calling the requestWindowFeature() method must take effect before calling the setContentView() method. In the code, the entire DecorView is added to the PhoneWindow only after ActivityManagerService calls the onResume() method when the setContentView() method is called in the onCreate() method. And let it display, and finally finish the interface drawing.

The View of measurement

Before drawing a View, the Android system must also measure the View and tell the system how big the View should be. This is done in the onMeasure() method. The Android system provides a class — the MeasureSpec class — to help us measure the View. MeasureSpec is a 32-bit INT value where the high 2 bits are the measured mode and the low 30 bits are the measured size.

The measurement mode can be the following three:

  • EXACTLY

When the control’s layout_width or layout_height property is specified as a specific value, such as Android :layout_width = “100dp”, or match_parent (occupying the size of the parent View), The system uses EXACTLY mode.

  • AT_MOST

When the layout_width or layout_height property of a control is specified as WRAP_content, the size of the control generally changes with the changes of its child controls or content, as long as the size of the control does not exceed the maximum size allowed by the parent control.

  • UNSPECIFIED

This property is odd — it doesn’t specify its size measurement mode, the View can be as big as it wants, and is usually used when drawing custom views.

With the MeasureSpec class, we get the View’s measurement mode and the size the View wants to draw. With this information, we can control the final display size of the View.

** Overriding the onMeasure() method

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Copy the code

Hold down the Ctrl key in the IDE to view the super.onmeasure () method. The measurement is completed by calling setMeasuredDimension(int widthMeasureSpec, int widthMeasureSpec) to set the measured width and height. So after overwriting the onMeasure() method, the final thing to do is set the measured width and height as an argument to the setMeasuredDimension() method. Based on the above analysis, the onMeasure() method code is rewritten as follows:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(
            measureWidth(widthMeasureSpec),
            measureHeight(heightMeasureSpec));
}
Copy the code

In the onMeasure() method, we call our custom measureWidth() and measureHeight() methods to redefine the width and height, respectively, as MeasureSpec objects. MeasureSpec objects contain both the measurement mode and the measured value.

  • The first step is to extract the specific measurement mode and size from the MeasureSpec object as follows:
int specMode = MeasureSpec.getMode(measureSpec);  // Select the measurement mode
int specSize = MeasureSpec.getSize(measureSpec);  // take out the measurement size
Copy the code
  • The second step is to give different measured values by judging the measurement mode.

When specMode is EXACTLY, use the specified specSize. When specMode is for the other two modes, a default size is required. If you specify the WRAP_content attribute, that is, the AT_MOST mode, you need to use the smaller values of the size and specSize we specified as the final measurements. The measureWidth() method looks like this, which is basically template code:

private int measureWidth(int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    if(specMode == MeasureSpec.EXACTLY) {
        result = specSize;
    } else {
        result = 200;  // The default value is 200 if the mode is not exact
        if(specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); }}return result;
}
Copy the code

The measureHeight() method is basically the same as masureWidth(). The program effect is shown below.

The View of the drawing

Once we have a View measured, we can simply override the onDraw() method and draw the desired graphics on the Canvas. A Canvas is like a drawing board, and you can use Paint to draw on it.

** Rewrite the onDraw() method:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}
Copy the code

We notice that onDraw() has one argument, which is the Canvas Canvas object. This Canvas object is used to draw, whereas elsewhere you usually create a Canvas object using code like this:

Canvas canvas = new Canvas(bitmap);
Copy the code

When creating a Canvas object, we usually pass in a Bitmap object, which will be closely associated with the Canvas picture through this Bitmap object. This process is called loading the Canvas. This Bitmap is used to store information about all pixels drawn on the Canvas. So when you create a Canvas object in this way, all calls to canvas.drawxxx () will occur on this bitmap.

In the onDraw() method of the View class, we can see the direct relationship between canvas and bitmap through the following code. First draw two bitmaps in the onDraw method as follows:

canvas.drawBitmap(bitmap1, 0.0.null);
canvas.drawBitmap(bitmap2, 0.0.null);
Copy the code

For bitmap2, we load it into another Canvas object as follows:

Canvas mCanvas = new Canvas(bitmap2);
Copy the code

Elsewhere draw on a Canvas loaded with Bitmap2 using the Canvas drawing method as follows:

mCanvas.drawXXX
Copy the code

When you draw on bitmap2 using mCanvas and refresh the View, you will find that the onDraw() method has changed bitmap2 because bitma2 carries the drawing operations done on mCanvas. We also use the Canvas drawing API, but instead of drawing directly on the screen specified by the onDraw() method, we change the bitmap and have the View redraw it to display the changed bitmap.

The measurement of ViewGroup

As mentioned in the previous analysis, a ViewGroup manages its children. One of the management items is the display size of the children. When the ViewGroup size is wrAP_content, the ViewGroup needs to traverse the child views to get the sizes of all the child views to set its own size. In other modes the size is set to a specified value. ViewGroup obtains the measurement results of each sub-view by traversing all sub-views and calling the Measure method of sub-views. When the child View measurement is completed, you need to put the child View in the appropriate position, this process is the View Layout process. ViewGroup in the execution of Layout process, the same is the use of traversal call Layout method of child View, and specify its specific display position, so as to determine its Layout position. When customizing a ViewGroup, it is common to override the onLayout() method to control the logic of the display position of its children. Also, if it wants to support the wrap_content property, it must override the onMeasure() method, as with View.

The ViewGroup to draw

A ViewGroup usually doesn’t need to be drawn because it has nothing to draw, and ViewGruop’s onDraw() method won’t even be called unless the background color of the ViewGroup is specified. However, the ViewGroup uses the dispatchDraw() method to draw its children, again by iterating through all the children and calling their drawing methods.