process
-
Explain why the problem occurred in the last video?
-
Why do we need to measure?
-
What is the measure mechanism?
-
Implement the correct measurement of the View.
The View in the previous section was different from what you expected, right?
In the previous section, we set the height of the custom control in the Mian_Layout to wrap_content, but the result is not what we expected. Why?
This is due to the Measure mechanism of Android View.
The View goes through three stages from loading to rendering to the screen:
-
The measure mechanism measures the size of each View to ensure that the View is properly displayed.
-
The Layout mechanism (ViewGroup specific, not common View) puts each child View in the right place.
-
Draw mechanism, accurately draw the content of each View.
The problem in the previous section was with the measure mechanism, which did not handle the view measurement.
Measure process approximately
The final size of a View not only determines the measurement of the View itself, but also has a direct relationship with its parent View.
Here involves the source code analysis, temporarily did not write, only about the process. So if you look at this, it’s a little bit easier to look at
-
The parent View obtains the height and width of the child View (that is, the laytout_width and layout_height values set in laytout_width and layout_height values), and then determines the measurement mode and initial width and height of the child View based on the parent View’s measurement mode (because it can be changed in onMeasure).
Three kinds of width and height mode, and finally give the child view mode by the parent view and child view combination.
- EXACTLY: EXACTLY, given the size specified by View,
- AT_MOST: the maximum value is the size of the parent View, and the smallest possible measurement is squeak = itself.
- UNSPECIFIED: The parent of the current View does not limit the size of the View.
-
The parent View calls the child’s measure() method and then calls the onMeasure() method to perform the measurement.
-
If the custom View does not override the onMeasure method, the onMeasure() method of the base View will be used to handle the measurement.
In the last section, we did not rewrite the onMeasure method, but the system directly used the base class View for processing. Here we give the source code of ViewGroup to determine the measurement mode of sub-view according to its own measurement mode and the height and width of sub-view, so we can understand why the problem appeared in the last section.
Updated June 19, with emphasis on !!!! Different viewgroups have different implementations. For example, scrollable viewgroups do not limit the size of the scrolling direction when the subview mode is WRAP_CONTENT (see RecyclerView). Here is the q&A of the ocean gods, which explains this pattern in detail.
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { // The parent View gets its own measurement mode and size int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; // The parent View determines the final measurement mode and size of the child View according to its measurement mode and the measurement mode of the child View switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: // Parent view is exact mode, corresponding to exact value and match_parent 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. In the previous section, we set the height of the custom view to wrap_content mode, and the parent view directly sets the final size of the child view to its own size. In the custom view, we do not handle onMeasure, so the final height of the view is the height of the parent view. 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); } Copy the code
Now formally process the measure procedure.
Measure processing is simple
The general View measurement process is actually a set of template code, if there is no special measurement requirements of the basic code is consistent, the process is relatively simple.
1. Override the onMeasure method
Overwrite the onMeasure method without calling the method of the superclass. We need to control the whole measurement process accurately.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}Copy the code
2. Handle height and width separately
Height and width are handled logically the same, but the code is slightly different.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureWidth(widthMeasureSpec);
}
private int measureWidth(int widthMeasureSpec) {
// Measure the width
// Get the width mode
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
// Get the recommended width of the parent View
int recommendWidth = MeasureSpec.getSize(widthMeasureSpec);
// The final width
int finalWidth = recommendWidth;
switch (widthMode) {
case MeasureSpec.EXACTLY:
// The exact value is given
break;
case MeasureSpec.AT_MOST:
// This corresponds to wrap_content, but the parent view sets the size to its own size, so we need to handle it ourselves
// The processing logic is the minimum size we set ourselves + the corresponding size of the inside margin, margins do not matter
finalWidth = radius*2 + getPaddingLeft() + getPaddingRight();
break;
case MeasureSpec.UNSPECIFIED:
// There is no limit to the size of the parent view
break;
}
return finalWidth;
}
Copy the code
3. Call the setting size function to complete the measurement
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),measureHieght(heightMeasureSpec));
}
Copy the code
The effect is as follows: