Read The Fucking Source Code
The introduction
Android custom controls involve the process of drawing and distributing views
Android Q — API 29
[Android drawing process]
1. An overview
2. MeasureSpec
2.1 introduction of MeasureSpec
- MeasureSpec = Measurement mode + measurement size.
- MeasureSpec the value in MeasureSpec is an integer (32 bits), and the top 2 bits are mode (why only 2 bits? Because there are only three states, that’s enough), the lower 30 bits is size. This design is mainly for memory optimization.
2.2 MeasureSpec Three modes
- UPSPECIFIED (unspecified mode) : The parent has no restrictions on its children, which can be as large as they want
- EXACTLY: The parent container has set the dimensions for the children, and the children should obey these boundaries, no matter how much space the children want.
- AT_MOST (maximum mode) : Child containers can be any size within the declared size
2.3 Where does MeasureSpec come from?
2.3.1 MeasureSpec distributed by the top-level (DecorView
There are only two modes for the top-level distributed Measure: EXACTLY/AT_MOST. Why is that? Because the UPSPECIFIED schema has no mapping in XML, it can only be set in code, and the DecorView simply loads the layout from THE XML, which will be explained specifically to UPSPECIFIED later.)
Let’s look at the getRootMeasureSpec() method in view argument PL
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; The switch (rootDimension) {/ / width/height set in the XML for MATCH_PARENT case ViewGroup. LayoutParams. MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; / / XML in the width/height set to WRAP_CONTENT case ViewGroup. LayoutParams. WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; // Set the width/height in the XML to specific values dp/px default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }Copy the code
2.3.2 MeasureSpec calculation of parent View to child View
The child View’s MeasureSpec value is calculated from the child View’s layout parameter (LayoutParams) and the parent container’s MeasureSpec value.
ViewGroup getChildMeasureSpec(); getChildMeasureSpec();
// MeasureSpec public static int getChildRespec (int spec, int padding, Int childDimension) {childDimension = MeasureSpec (spec); // Parent view size int specSize = MeasureSpec. GetSize (spec); Int size = math. Max (0, specsie-padding); ResultSize = 0; // resultSize = 0; int resultMode = 0; // Parent View switch (specMode) {// Parent has exact size on us // Parent View is exact. 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. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has a maximum size imposed on us // Parent has a maximum size imposed on us // Case MeasureSpec. 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 // The Parent View is UNSPECIFIED. 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
Summary of policy patterns for subviews
2.3.3 Child View MeasureSpec calculation
Let’s look at the getDefaultSize() method in View
public static int getDefaultSize(int size, int measureSpec) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); Switch (specMode) {// If the policy mode is UNSPECIFIED, the size of the input parameter is UNSPECIFIED. The default value is 0. Case MeasureSpec. break; AT_MOST: Case MeasureSpec.AT_MOST: Case MeasureSpec. break; } return result; }Copy the code
The MeasureSpec of the child View above can lead to the following question:
What happens if the onMeasure method in a custom View does not handle wrap_content? Why is that? How to solve it?
- If the View layout parameter is set to wrap_content and the parent View is AT_MOST/EXACTLY, the mode of the corresponding View is AT_MOST.
- The width and height of the View returns the size obtained from MeasureSpec (the size of the parent View).
- The wrap_content effect of a View is the same as that of match_parent.
- The solution is to override the onMeasure to do something special with AT_MOST, such as give the default width.
2.3.4 Separate description of the UNSPECIFIED mode
As mentioned earlier, the UNSPECIFIED mode is unmapped in the XML layout declaration and can only be set from the code layer. What are the specific application scenarios? Example: ListView/ScrollView.
Overrides the measureChildWithMargins() method for ViewGroup in ScrollView.
@Override protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed; Son / / take the initiative to set up the View of strategy pattern for UNSPECIFIED final int childHeightMeasureSpec = MeasureSpec. MakeSafeMeasureSpec (Math. Max (0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal), MeasureSpec.UNSPECIFIED); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }Copy the code
UNSPECIFIED mode can be used if you want to customize your controls later, such as using DragHelper to implement the SystemUi-like drop-down StatusBar.
2.3.5 MeasureSpec summary
Measure distribution of a View, in other words, computes the final MeasureSpec to determine the final View size. So we do measure multiple times. And this is something that you’ll see from the log as you develop.
3. measure
3.1 Differences between a View and a ViewGroup
View: Measure yourself and save. ViewGroup: Recursively traverses all child views, measures the size of child views, and saves; Calculate and save its own size based on the size of all child Views.
3.2 Summary of Views and ViewGroups
3.2.1 Measure Process of View
3.2.2 Measure Process of the ViewGroup
3.3 Problem Thinking
What are the differences and relationships between the measure method of View and the onMeasure method?
- The measure method uses final to indicate that it cannot be modified, whereas the onMeasure method can be overridden by subclasses as needed.
- The measure method is used to detect cached data and compare whether to measure it again.
- The onMeasure method is used to read the measurement rules of the parent layout and customize its own as required, calling setMeasuredDimension to determine its own size.
Do you override onMeasure methods in ViewGroup? Why is that?
- A ViewGroup does not override onMeasure methods by default. The task of overriding onMeasure methods is assigned to subclasses of the ViewGroup.
- Different ViewGroup subclasses (LinearLayout, FrameLayout, etc.), their layout requirements are often different, that onMeasure method to rewrite their own.
Why can’t a ViewGroup’s measure process implement onMeasure uniformly as a single View’s measure process does?
- OnMeasure () = Measure the width/height of the View.
- Because different ViewGroup subclasses (LinearLayout, RelativeLayout/Custom ViewGroup subclasses, etc.) have different layout properties, their subviews measure differently.
- In a single View measure process, getDefaultSize() simply measures the width and height, but sometimes more detailed measurements are needed in practice. So sometimes you need to override onMeasure().
- In custom ViewGroups, the key is to override onMeasure() as needed to implement your child View measurement logic.
Why is the View test method called multiple times? When is the result of getMeasuredWidht/Height() and getWidht/Height() inconsistent?
- View measurement method will be called many times because the measurement results before and after may not be consistent, compared to the weight of the LinearLayout, the first measurement result of View and the final measurement result is definitely different.
- The getMeasuredWidht/Height() and getWidht/Height() views are mostly the same, but one exception is resetting the View’s position size in the Layout method, This changes the width and height of the View because it is the setFrame method that ultimately determines the actual size and position of the View.
- The value obtained by the getMeasuredWidth method is the value set by the setMeasuredDimension method, which is determined after the Measure method runs.
- The getWidth method is the mright-mleft of the four parameters passed in the Layout method, whose value is determined after the Layout method is run.
- Normally, the getMeasuredWidth method is used in the onLayout method, and the getWidth method is used elsewhere.
Xiaobian extension links
Android View Module Family Bucket
Recommended Blogs
Customizable View Measure (measuredWidth, getWidth, measureDWidth, MeasureDWidth) Android application layer View drawing process and source code analysis