Reflection series blog is my attempt to learn a new way, the series of origin and table of contents please refer to here.
An overview of the
Android itself View system is very grand, the source code is worth thinking and learning from many, to View itself drawing process as an example, after measure measurement, layout, draw drawing three processes, finally can be drawn out and displayed in front of the user.
This paper will systematically summarize the design idea of the measurement process in the drawing process, and readers need to have a preliminary understanding of the knowledge related to measure() of View:
The overall train of thought
The nature of the View measurement mechanism is very simple. As the name suggests, its purpose is to measure the width and height of the control. Around this purpose, the View designer has woven a set of complex logic through the code:
-
1. For the child View, its width and height are directly limited by the layout requirements of the parent View. For example, the parent View is limited to a width of 40px, and the maximum width of the child View is also limited to this value. Therefore, when measuring a child View, the child View must know its parent View’s layout requirements, which are described in Android using the MeasureSpec class.
-
2, for the complete measurement process, the parent control must depend on the child control width and height measurement; If the child control itself is not measured, the parent control itself is not measured. The measurement process of View in Android uses a very classical recursive idea: For a complete interface, each page maps a View tree, and the parent control at the top of the measurement will pass its layout requirements to the child control to start the measurement of the child control, and the child control will pass its layout requirements to its own child control by traversing during the measurement process. And so on down to the lowest control… This way of passing data from top to bottom by traversing is called the “pass” flow in the measurement process. And when the child controls bottom position measurement has been completed, will his father controls all child controls the width of high data aggregation, and then through the corresponding measurement strategy to calculate the parent control itself is high, wide measurement has been completed, the parent of the parent can also according to the result of the measurement of all of its child controls to measure itself, this from the bottom up their respective measurements, The measurement method of the topmost parent control is finally completed, which is called the “return” process in the measurement process. At this point, the whole View tree measurement of the interface is completed.
For developers not very familiar with drawing process, the text seems to be difficult, but this text summarizes the essence is drawing process of the whole design idea, the reader should not be regarded as the source code, this paper analyses, and should be generation itself into the process of design, when the deep understanding of the whole process of design thinking, The design and writing of the code of the measurement process is done in a single flow.
layout
Layout requirements are a very important core term throughout the measurement process and are described in Android using the MeasureSpec class.
Why is the layout requirement important, and how is it defined? The setMeasureDimension() function is provided as a parameter to measure the View’s width and height. If you want to measure the View, you can use setMeasureDimension() to measure the View’s width and height.
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
MeasuredWidth Specifies the measuredWidth of the View
// measuredHeight Indicates the measuredHeight of the View
// omit other code...
// The essence of this method is to store the measurement results so that the width and height of the control can be obtained in the subsequent layout and DRAW processes
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
}
Copy the code
Note that the child control’s measurement process itself should also depend on some layout constraints of the parent control, such as:
- 1. The parent control is only fixed in width and height
${x}px
, the child control is set tolayout_height="${y}px"
; - 2. The parent control height is
wrap_content
The child control is set tolayout_height="match_parent"
; - 3. The parent control height is
match_parent
(Fill), the child control is set tolayout_height="match_parent"
;
These cases, because can’t calculate the accurate control itself wide and high value, simply by setMeasuredDimension () function seems unlikely to achieve the purpose of measuring control, because the child control measurements are decided jointly by the parent and its itself (this article will explain), and the layout of the subsidiary to the parent control constraints, This is the layout requirement mentioned earlier, the MeasureSpec class.
MeasureSpec class
From an object-oriented perspective, we design the MeasureSpec class as follows:
public final class MeasureSpec {
int size; // Measure the size
Mode mode; // Measurement mode
enum Mode { UNSPECIFIED, EXACTLY, AT_MOST }
MeasureSpec(Mode mode, int size){
this.mode = Mode;
this.size = size;
}
public int getSize(a) { return size; }
public Mode getMode(a) { returnmode; }}Copy the code
During the design process, we divided the layout requirements into two attributes. Measuring size means that the control needs the width and height corresponding to the size, and measuring mode means the width and height mode corresponding to the control:
- UNSPECIFIED: The parent element imposes no constraints on the child elements, which can be of any desired size. Daily development of custom View does not consider this mode, can be temporarily ignored;
* EXACTLY: The parent determines the exact size of the child element, and the child element will be bounded within the given boundary regardless of its size; The width or height of the control is set tomatch_parent
Or specify the size, for example20dp
;- AT_MOST: the child element has a value of at most a specified size; The width or height of the control is set to
wrap_content
.
Neatly, Android doesn’t describe the layout requirements in the same way that we define MeasureSpec objects. Instead, Android uses a simpler binary way, substituting a 32-bit int:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30; // The shift bit is 30
// Int is 32 bits, shifted 30 bits to the right, this attribute represents the mask value, used with size and mode "&" to obtain the corresponding value.
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// the value of 00 + (30 bits 0)
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// the value of 01 + (30 bits 0)
public static final int EXACTLY = 1 << MODE_SHIFT;
// the value of 10 + (30 bits 0)
public static final int AT_MOST = 2 << MODE_SHIFT;
// Create a measurement requirement based on size and mode
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}
// Select * from mode;
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
// Extract size according to specification
public static int getSize(int measureSpec) {
return(measureSpec & ~MODE_MASK); }}Copy the code
In this int value, the first two bits represent the measurement mode, and the last 30 bits represent the measurement size. For the mode and size value, only bit operation is needed.
For example, if we set the width =5px (binary = 101), then mode corresponds to EXACTLY. When creating the measurement requirement, just add the binary to get the int value that stores the relevant information:
MeasureSpec = measureSpec = MODE_TASK = measureSpec = MODE_TASK = measureSpec = MODE_TASK = measureSpec = MODE_TASK
MeasureSpec = measureSpec = MODE_TASK = measureSpec = MODE_TASK = measureSpec = MODE_TASK = measureSpec = MODE_TASK
The MeasureSpec class now gives you an idea of how the width or height of a View is actually described in a 32-bit int during Android drawing, and the MeasureSpec class itself is just a container for static methods.
So far, the layout requirements represented by the MeasureSpec class have been introduced, and we’ll look at them here, as they play a crucial role in the overall measurement process that follows.
Measuring a single control
Considering only the measurement of a single control, the whole process needs to define three important functions, respectively:
final void measure(int widthMeasureSpec, int heightMeasureSpec)
: a function that performs measurement;void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
: To actually perform measurement functions, developers need to implement their own custom measurement logic;final void setMeasuredDimension(int measuredWidth, int measuredHeight)
: the function that completes the measurement;
Why do we need to define these three functions?
1. Measure () entry function: marks the beginning of measurement
First of all, the parent control needs to call the measure() function of the child control and pass in the width and height layout requirements as parameters, marking the beginning of the measurement of the child control itself:
// This is the parent control code, let the child control start measuring
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
Copy the code
For the measurement process of View, it must contain two parts: the public logic part and the developer’s custom measurement logic part. In order to ensure the security of the code of the public logic part, the designer configures the final modifier of measure() method:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
/ /... Public logic
// Developers need to rewrite the onMeasure function themselves to customize the measurement logic
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Copy the code
Developers cannot override the measure() function and expose the View’s custom measurement strategy by defining a new onMeasure() interface for developers to override.
2. OnMeasure () function: customize the View measurement strategy
In the onMeasure() function, the View itself also provides a default measurement strategy:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Copy the code
Using width as an example, get the View’s default width like this:
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)
- 1. Under certain circumstances (e.g
minWidth
orbackground
Attributes),View
Need to pass throughgetSuggestedMinimumWidth()
Function as the default width value:
protected int getSuggestedMinimumWidth(a) {
return (mBackground == null)? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }Copy the code
- 2. After that, pass the result as a parameter to
getDefaultSize(minWidth, widthMeasureSpec)
In the function, according tolayoutTo calculate theView
Last measured width value:
public static int getDefaultSize(int size, int measureSpec) {
// The default value for width
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
// According to the different measurement mode, the returned measurement result is different
switch (specMode) {
// Any mode, width is the default value
case MeasureSpec.UNSPECIFIED:
result = size;
break;
// match_parent and wrap_content return the size values in the layout requirements
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
Copy the code
In the code above, the View’s default measurement strategy also confirms that even if the View is set to layout_width=”wrap_content”, its width will fill the parent layout (the same effect as match_parent), and its height will remain the same.
3. SetMeasuredDimension () function: Indicates completion of measurement
The setMeasuredDimension(Width, height) function is very important. If onMeasure() executes a custom measurement strategy, the setMeasuredDimension(width, height) function is called to indicate that the View has measured the result:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Generally, setMeasuredDimension() marks the end of the measurement
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
MeasuredWidth Specifies the measuredWidth of the View
// measuredHeight Indicates the measuredHeight of the View
// omit other code...
// The essence of this method is to store the measurement results so that the width and height of the control can be obtained in the subsequent layout and DRAW processes
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
}
Copy the code
This function is designed to be protected final, meaning that it can only be called by subclasses and cannot be overridden.
Once the function is called, the developer can use either getMeasuredWidth() or getMeasuredHeight() to get the width and height measured by the View. The code looks something like this:
public final int getMeasuredWidth(a) {
return mMeasuredWidth;
}
public final int getMeasuredHeight(a) {
return mMeasuredHeight;
}
// How to use it
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight()
Copy the code
After measure() -> onMeasure() -> setMeasuredDimension() is called, the View measurement process is complete.
Complete measurement process
For a complete interface, each page is mapped to a View tree, see, understand the measurement process of a single View, from a macro point of View, View tree overall measurement process will be how to achieve?
1. Design ideas
Each ViewGroup subclass has a different measurement strategy (the logic inside the onMeasure() function). For example, a RelativeLayout or LinearLayout has a different measurement strategy, but the overall idea is similar. That is to traverse all the child controls and calculate the width and height according to the parent control’s own measurement strategy and get the measurement results.
Take the vertical LinearLayout as an example, how to complete the LinearLayout height measurement? In this paper, the LinearLayout height measurement strategy is simply defined as iterating to obtain all the child controls, adding up the heights, and the resulting value is the measurement result of its own height. If you don’t know the height of each child control, the LinearLayout naturally cannot measure its own height.
So for the overall measurement of the View tree, the measurement of the control is actually bottom-up, as described in the overall Thinking section at the beginning of this article:
For the whole measurement process, the parent control must depend on the measurement of the width and height of the child control; If the child control itself is not measured, the parent control itself is not measured.
Also, because the measurement logic of the child control is limited by the layout requirements passed by the parent control, the overall logic should be:
-
- At the beginning of the measurement, the parent control of the top layer will pass the layout requirements to the child control to inform the child control to start the measurement;
-
- The child control calculates its own layout requirements according to the measurement strategy, and then passes it to the next level of child control, notifying the child control to start measuring, and so on, until reaching the last level of child control;
-
- Execute when the last level of child controls are measured
setMeasuredDimension()
Function, the parent control, according to its own measurement policy, will allchild
The width, height and layout attributes of theLinearLayout
Is to calculate the sum of all child control height), get its own measurement width height;
- Execute when the last level of child controls are measured
-
- The control is called by
setMeasuredDimension()
The function completes the measurement, after which its parent control completes the measurement according to its own measurement strategy, and so on until it completes the top-level levelView
From there, the entire page is measured.
- The control is called by
The design here reflects the classical recursive idea. Step 1 and step 2, the notification of starting measurement from top to bottom, we call it the recursive process of measurement steps; step 3 and step 4, the order of measurement is from bottom to top, we call it the recursive process of measurement steps.
2, the implementation of the process
Now according to the design ideas in the previous section, start to code the implementation of the recursive process.
The layout requirements represented by MeasureSpec play a crucial role in the overall recursive process, and you can understand why it is common to say that the measurement results of a child control are determined by both the parent control and itself.
Taking the vertical LinearLayout as an example, we need to traverse and measure all of its child controls. Therefore, in the onMeasure() function, we code as follows for the first time:
// Version 1.0 LinearLayout
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 1. Measure each child by traversing
for(int i = 0 ; i < getChildCount() ; i++){
View child = getChildAt(i);
// 2. Directly measure child controls
child.measure(widthMeasureSpec, heightMeasureSpec);
}
// ...
// 3. All child controls are measured...
// ...
}
Copy the code
Here we focus on the int heightMeasureSpec parameter. We know that this 32-bit int contains the layout requirements for the height passed by the parent: the measured size and mode. Now, if the parent layout is passing the size of the screen height, is it reasonable to execute child.measure(widthMeasureSpec, heightMeasureSpec) as a parameter and let the child control start measuring directly?
The answer is no. Imagine a simple scenario where the LinearLayout itself has a padding value, and the maximum height of the child controls is no longer as large as the Size of heightMeasureSpec. The child control can then take the height of the screen from the heightMeasureSpec parameter and use setMeasuredDimension() to set its own height to match that of the parent control — which causes the padding configuration to fail as expected.
Therefore, we need to design an additional rewritable function for customizing the measure of the child:
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// Gets the layout parameters of the child elements
final LayoutParams lp = child.getLayoutParams();
// Use the padding value to calculate the layout requirements of the child control
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// Pass the new layout requirements to the measure method to measure the child controls
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code
We solve this problem by defining the measureChild() function, which calculates the layout requirements of the child control and then passes the new layout requirements to the child control, which then measures according to the new layout requirements. This explains why the measurement results of the child control are determined by both the parent control and itself.
Here we notice that we have designed a getChildMeasureSpec() function. What does this function do?
GetChildMeasureSpec () function
The getChildMeasureSpec() function calculates the child control’s MeasureSpec and the padding values of the parent layout. Since the logic of this function is reusable, we define it as a static function:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// Get the measurement mode of the parent View
int specMode = MeasureSpec.getMode(spec);
// Get the measurement size of the parent View
int specSize = MeasureSpec.getSize(spec);
// The size of the child View calculated by the parent View. The child View may not use this value
int size = Math.max(0, specSize - padding);
// Declare variables to hold the size and mode of the child View actually computed
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// If the mode of the parent container is Exactly the exact size
case MeasureSpec.EXACTLY:
// The height or width of the child View >0 indicates an exact value, since match_parent and wrap_content are <0
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
The height or width of the child View is match_parent
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;// Assign size to resultSize which is the size of the parent View minus the margin value
resultMode = MeasureSpec.EXACTLY;// Set the measurement mode of the subview to EXACTLY
// The height or width of the child View is wrap_content
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;// Assign size to result
resultMode = MeasureSpec.AT_MOST;// Set the measurement mode of the child View to AT_MOST
}
break;
// If the measurement mode of the parent container is AT_MOST
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
// Since the size of the parent View is limited by the limit value, the size of the child View should also be limited by the size of the parent and should not exceed the size of the parent View
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// If the measurement mode of the parent is UNSPECIFIED, the size of the parent is UNSPECIFIED
case MeasureSpec.UNSPECIFIED:
// If the width and height of the View are exact values
if (childDimension >= 0) {
// The size of the subview is the exact value
resultSize = childDimension;
// The measurement mode is EXACTLY
resultMode = MeasureSpec.EXACTLY;
// The child View's width or height is match_parent
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Since the size of the parent View is undetermined, so is the size of the child View
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
// Call the makeMeasureSpec method based on the resultSize and resultMode to get the measurement requirements and return them as the value
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
Copy the code
There are relatively more logical branches, and the calculation logic required by the layout of the child control has been written clearly in the notes, which is summarized as follows:
Why is this function important? Because this function is the measurement result of the child control is determined by the parent control and its own most direct embodiment, at the same time, in different layout modes (match_parent, WRAP_content, specify dp/ PX), its corresponding child control layout requirements return value is also different, recommend readers carefully understand this code.
Returning to the previous section, now we define the onMeasure() method as follows:
// Version 2.0 of LinearLayout
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 1. Measure each child by traversing
for(int i = 0 ; i < getChildCount() ; i++){
View child = getChildAt(i);
// 2. Calculate the new layout requirements and measure the child controls
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
// ...
// 3. All child controls are measured...
// ...
}
Copy the code
3, the realization of the process
Now that all child controls are measured, it is easy to implement the flow by adding all child heights and calling setMeasuredDimension() to end the measurement:
// Version 3.0 LinearLayout
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 1. Measure each child by traversing
for(int i = 0 ; i < getChildCount() ; i++){
View child = getChildAt(i);
// 2. Calculate the new layout requirements and measure the child controls
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
// 3. Complete the measurement of the child control and add the height
int height = 0;
for(int i = 0 ; i < getChildCount() ; i++){
height += child.getMeasuredHeight();
}
// 4. Complete the LinearLayout measurement
setMeasuredDimension(width, height);
}
Copy the code
At first glance, it seems difficult to reflect the recursion of the whole process. In fact, when we sort out our thoughts from the top of the View tree, the execution order of the code logic is clear at a glance:
As shown in the figure, the solid line represents the overall top-down recursive flow in the measurement process, while the dotted line represents the bottom-up regression flow.
At this point, the whole measurement process is completed.
reference
- The Android source code
- Android development art exploration
- Android measure process, WRAP_CONTENT and XML layout file parsing process analysis
- Android hand take you through the work process of a custom View
About me
If you think this article is valuable to you, welcome to ❤️, and also welcome to follow my blog or Github.
If you feel that the article is not enough, please also urge me to write a better article by paying attention to it — in case I make progress one day?
- My Android learning system
- About the article error correction
- About paying for knowledge
- About the Reflections series