preface
In the process of drawing a View, measure is the first step. The View first needs to measure to get the specific length and width, which is just like drawing a picture first needs to get the approximate width and height of the object to be painted.
Remember the performTraversals method in the top-level view?
If you forget, you can take a look at the View system in Android in the previous article
PerformTraversals is a method with nearly a thousand lines of code, so it is not necessary to go through it in order to understand the View rendering process, but to analyze the core method of rendering
Source code analysis
The measure of the entrance
In the performTraversals method there will be a method like this:
WindowManager.LayoutParams lp = mWindowAttributes;
// Get the relevant size parameters
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// As the name implies, this is a method that executes a measure
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
Copy the code
MeasureSpec: MeasureSpec: MeasureSpec: MeasureSpec: MeasureSpec: MeasureSpec: MeasureSpec: MeasureSpec
About measurement specification MeasureSpec
MeasureSpec can be understood as a criterion for measuring a View. MeasureSpec is an inner class of a View and consists of two parts:
MeasureSpec = Measurement mode + measurement size
- Measurement specification: 32 bit Int type
- Measurement mode: 2 bits higher than the measurement specification
- Measurement size: 30 bits lower than the measurement specification
There are three measurement modes:
Specifications and types | The specification |
---|---|
UNSPECIFIED | The parent View does not restrict the size of the child View, which is usually used in list-based controls, but not in custom views |
EXACTLY | The parent view specifies a certain size for the child view. This is used when the control is of fixed size or match_parent, in which case the parent view can directly determine the size of the child view. |
AT_MOST | The parent view specifies a maximum size for the child view. This is usually applied to the wrAP_content control. In this case, the parent view cannot get the size of the child view, only the child view can get it. |
So the MeasureSpec class can be understood as an abstraction of the View dimension specification for subsequent measurement calculations.
So let’s continue our process analysis.
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
try {
// This mView is the layout we define ourselves by passing in our setContentView
// Measure method is called by View. Start measure process
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code
Analysis 1. Start the measure process
// View's measure method
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if(optical ! = isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets();int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Analyze the response process to measureonMeasure(widthMeasureSpec, heightMeasureSpec); ...}Copy the code
In the method initiated by measure, the measurement specification of width and height is first obtained and then passed to the method responding to measure.
2. Analyze the response process to measure
// View's onMeasure method
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Set the measure size
// The parameter of this method is the measured width and height, but the method of obtaining the parameter value is complicated
/ / getSuggestedMinimumWidth () & getSuggestedMinimumHeight () method
// getDefaultSize(int size, int measureSpec
// setMeasuredDimension(int measuredWidth, int measuredHeight)
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Copy the code
GetSuggestedMinimumWidth () & getSuggestedMinimumHeight () analysis
Get the minimum control width and height depending on whether there is a background
protected int getSuggestedMinimumWidth(a) {
return (mBackground == null)? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }Copy the code
protected int getSuggestedMinimumHeight(a) {
return (mBackground == null)? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }Copy the code
GetDefaultSize (int size, int measureSpec
// Get the default size according to the minimum width and height obtained and the measurement criteria
public static int getDefaultSize(int size, int measureSpec) {
// Minimum size
int result = size;
// Measurement mode
int specMode = MeasureSpec.getMode(measureSpec);
// Measure the size
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED: // If you are not restricted to use the minimum size
result = size;
break;
case MeasureSpec.AT_MOST: // Measure size is used if the size is specified or limited
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
Copy the code
SetMeasuredDimension (int measuredWidth, int measuredHeight) analysis
// measuredWidth & measuredHeight - measuredWidth and measuredHeight
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
// Check whether it is a ViewGroup
boolean optical = isLayoutModeOptical(this);
// Check whether the parent layout is ViewGroup
if(optical ! = isLayoutModeOptical(mParent)) {// Get visual width and height (depending on whether there is a background)
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
// Perform further calculations based on whether it is a ViewGroup
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
// Pass the final measurement width to the setMeasuredDimensionRaw method
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
Copy the code
SetMeasuredDimensionRaw (int measuredWidth, int measuredHeight) analysis
// View gets the measured width and height
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
Copy the code
Once the View has measured the mMeasuredWidth and mMeasuredHeight, we can move on to the next step, but first we need to look at the ViewGroup’s measure process.
ViewGroup drawing process
The onMeasure method of a ViewGroup is implemented by subclasses. For example, FrameLayout:
onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Get the number of child elements
int count = getChildCount();
final booleanmeasureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) ! = MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) ! = MeasureSpec.EXACTLY; mMatchParentChildren.clear();// Maximum width and height
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// Measure the Margins
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if(mMeasureAllChildren || child.getVisibility() ! = GONE) { measureChildWithMargins(child, widthMeasureSpec,0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if(lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); }}}}// Measure the padding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Compare the existing width and height with the background to get a larger value
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Compare the existing width and height with the foreground to get a larger value
final Drawable drawable = getForeground();
if(drawable ! =null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
// Pass in the measured width and height
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
// Iterate over child elements to measure
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0. getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY); }else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0. getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( height, MeasureSpec.EXACTLY); }else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
// The child controls the measurement operationchild.measure(childWidthMeasureSpec, childHeightMeasureSpec); }}}Copy the code
It is important to note that the measurement order of all viewgroups is the same. The order of different layout measurement child controls and measurement itself may change according to specific logic, which is not listed here. If you need to achieve a custom ViewGroup can be advanced to the source code to understand how similar ViewGroup is calculated.
conclusion
In general, measures of View can be divided into two categories:
- The View of the Measure
- The Measure of ViewGroup
The View of the Measure
The Measure process of a View is to calculate and store the width and height of the View according to the measurement specifications in onMeasure.
The ViewGroup Measue
The Measure process of a ViewGroup iterates the sub-views according to the logic of different viewgroups in onMeasure. By iterating the measures of the sub-views, the width and height of the ViewGroup are finally determined and stored.
In our daily custom View development, we usually copy the onMeasure method to get the measured width and height. If we do not want to use the parent method, we need to implement the specific logic, but we must call the setMeasuredDimension method to set the measured width and height
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// You can get the width and height of the measurement
}
Copy the code