preface

  • The customViewisAndroidThe basics developers must understand
  • There’s a lot about customization onlineViewPrinciples of the article, but there are some problems:Incomplete content, unclear ideas, no source code analysis, simple problems complex
  • Today, I’m going to summarize the principles of custom ViewsmeasureProcess. I can guarantee that this isThe most comprehensive, clear, and understandable on the market

directory


1. The role

Measure the View’s width/height

  1. In some cases, multiple measurements are required(measure)To determine theViewFinal width/height;
  2. In this case,measureThe width/height obtained after the process may be inaccurate;
  3. Suggestions: InlayoutIn the process ofonLayout()To get the final width/height

2. Stock up on knowledge

Before understanding measure process, there are three reserves of knowledge:

  1. The customViewBasic knowledge of
  2. ViewGroup.LayoutParamsClass ()
  3. MeasureSpecs

2.1 Basic knowledge reserve

See article: Custom View basics – the most easy to understand the principle of custom View series

2.2 ViewGroup. LayoutParams

  • Introduction to the

Layout parameter class

  1. ViewGroupA subclass of(RelativeLayout, LinearLayout)There’s a correspondingViewGroup.LayoutParamsA subclass
  2. Such as:RelativeLayouttheViewGroup.LayoutParams A subclass

= RelativeLayoutParams

  • role

Specifies layout parameters such as height and width for the View.

  • The specific use

Use the following parameters

parameter explain
The specific value dp / px
fill_parent Force the size of the child view to equal the size of the parent view (excluding padding)
match_parent Same as fill_parent, used for Android 2.3&later
wrap_content Resizing, forcing the view to expand to display all of its contents (including padding)
Android :layout_height="wrap_content" // Adaptive size Android :layout_height="match_parent" // Same height as the parent view Android :layout_height="fill_parent" // The same height as the parent view android:layout_height="100dip" // The exact height is set to 100dipCopy the code
  • The constructor

Constructor = entry to View, which can be used to initialize & get custom properties

Public DIY_View(Context Context){super(Context); } public DIY_View(Context context,AttributeSet attrs){ super(context, attrs); } public DIY_View(Context context,AttributeSet attrs,int defStyleAttr ){ super(context, attrs,defStyleAttr); // Default Style: // Specifies the default Style in the current Theme used by the Application or Activity and only applies when explicitly called. } public DIY_View(Context Context,AttributeSet attrs,int defStyleAttr,int defStyleRes){super(Context, attrs, DefStyleAttr defStyleRes); } // The most common ones are 1 and 2}Copy the code

2.3 MeasureSpec

The MeasureSpec class is a MeasureSpec class.


3. Measure the process

Measure process can be divided into two cases according to View type:

Next, I’ll examine these two measure processes in detail

3.1 Measure process of a single View

Application scenarios

In the absence of ready-made control View to meet the requirements, need to customize a single View.

  1. For example: create a support for loading network imagesImageViewcontrols
  2. Note: customViewIn most cases, there are alternatives: images/combined animations, but both can be too memory intensive, causing problems such as memory overruns.

The specific process

Source code analysis

/** * definition: a process entry; Is of view. Java class & final type, meaning subclasses cannot override this method. */ Public final void measure(int widthMeasureSpec, int heightMeasureSpec) {public final void measure(int widthMeasureSpec, int heightMeasureSpec) { int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { onMeasure(widthMeasureSpec, heightMeasureSpec); // Calculate view size ->> analyze 1} else {... } /** * 1: onMeasure() * Calculate the width/height of the View according to the measurement specification: getDefaultSize() * b. Store measured View width/height: SetMeasuredDimension () */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension() */ protected void onMeasure(int widthMeasureSpec, int HeightMeasure) { SetMeasuredDimension (getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec) getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); // setMeasuredDimension() : Obtain the View width/height measurements ->> Analysis 2 // Pass the parameters to getDefaultSize() ->> Analysis 3} /** * Analysis 2: SetMeasuredDimension () * Effect: Store measured View width/height * note: */ protected final void setMeasuredDimension(int measuredWidth, Int measuredHeight (measuredWidth = measuredWidth; // measuredHeight = measuredWidth; // measuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; } / / because the setMeasuredDimension (parameters) from getDefaultSize () / / to look below getDefaultSize () / * * * analyzed 3: getDefaultSize role () * : */ public static int getDefaultSize(int size, int measureSpec) {// Static int getDefaultSize(int size, int measureSpec) { // measureSpec: measureSpec // int result = size; Int specMode = MeasureSpec. GetMode (MeasureSpec); int specSize = MeasureSpec.getSize(measureSpec); Switch (specMode) {// With UNSPECIFIED mode, the provided default Size = parameter Size Case MeasureSpec.UNSPECIFIED: result = Size; break; // Size case measureSpec.AT_MOST: Case measureSpec.EXACTLY: result = specSize; break; } // Return View width/height value return result; }Copy the code

As mentioned above, when the mode of the test specification is UNSPECIFIED, the provided default size (size, the first parameter of getDefaultSize()) is used. So what exactly is the default size provided?

A: getSuggestedMinimumWidth ()/getSuggestedMinimumHeight (). See the following source code analysis.

protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth()); } // 1. If the View does not have a background, then the View width = mMinWidth // android:minWidth property specified value, if not 0. // 2. If the View has a background set, The width of the View for mMinWidth and mBackground getMinimumWidth () the maximum of / / below to continue watching mBackground getMinimumWidth source analysis / * * * () MBackground. GetMinimumWidth () source code analysis * / public int getMinimumWidth () {final int intrinsicWidth = getIntrinsicWidth (); . / / the mBackground getMinimumWidth () = the size of the background Drawable the return of the original width intrinsicWidth > 0? intrinsicWidth :0 ; // If there is no original width, then 0; }Copy the code

At this point, the width/height of a single View has been measured, that is, the measure process of a single View has been completed.

Source summary

Measures of a single View are described as follows.

The key to measuring width and height is getDefaultSize(), the measurement logic for which is shown below.


3.2 Measure Process of a ViewGroup

Application scenarios

Use existing components to form a new component based on a specific layout (that is, multiple child Views).

For example: the entry in the bottom navigation bar, generally is the upper icon (ImageView), the next text (TextView), then the two can be combined with a custom ViewGroup into a Veiw, providing two properties respectively used to set text and image, it will be more convenient to use.

Measuring principle

From ViewGroup to sub-view, traversal from top to bottom (namely tree recursion), by calculating the attributes of each View in the entire ViewGroup, so as to finally determine the attributes of the entire ViewGroup. That is:

  1. Traversal measures the size (width/height) of all child Views;
  2. Combine the dimensions (width/height) of all child views to obtain the measurements of the ViewGroup parent View.

The specific process

Note that if you want to customize a ViewGroup, you need to override onMeasure(), which is explained in the following section.

Source code analysis

/** * source code analysis: measure() * function: * 1. Judgment of basic measurement logic; * 2. Call onMeasure() Public final void measure(int widthMeasureSpec) Int heightMeasureSpec) {// Just show the core code //... int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key); If (cacheIndex < 0 | | sIgnoreMeasureCache) {/ / call onMeasure () - > view calculation analysis 1 onMeasure (widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { // ... } /** * onMeasure() {ViewGroup = onMeasure();} /** * onMeasure()Copy the code

According to the previous section, the measure process of a single View has a unified implementation of onMeasure() (code below), but why not the measure process of a ViewGroup?

/** * onMeasure() * function: a. Calculate the View width/height based on the View width/height measurement specification: getDefaultSize() * b. Store measured View width/height: SetMeasuredDimension () */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension() */ protected void onMeasure(int widthMeasureSpec, int HeightMeasure) { SetMeasuredDimension (getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec) getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); // setMeasuredDimension() : Obtain the View width/height measurements ->> Analysis 2 // Pass the parameters to getDefaultSize() ->> Analysis 3}Copy the code

The reason is: The onMeasure() method is used to measure the width/height of the View. Different viewgroups have different layout properties, which results in different View measurement methods. So the implementation of onMeasure() will also be different.

Therefore, ViewGroup cannot implement onMeasure() uniformly. This is also the biggest difference between a single View’s measure process and a ViewGroup’s measure process.

Autotype onMeasure ()

For Measure processes, the key to customizing viewGroups is to duplicate onMeasure() as required, so as to realize the measurement logic of sub-views. There are three steps for copying onMeasure() :

  1. Traversing all child Views and measurements: measureChildren()
  2. Merge the size of all child views, and finally obtain the measurement value of the ViewGroup parent View: need to customize implementation
  3. Store the measured View width/height values: setMeasuredDimension()

The details are as follows.

@override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Step 1: measureChildren(widthMeasureSpec, heightMeasureSpec); Void measureCarson{// Step 2: combine the size of all child views. // Step 3: Store the measuredDimension (widthMeasure, heightMeasure); /** * Analysis 1: measureChildren() * Effects: / / Protected void measureChildren(int widthMeasureSpec, int widthMeasureSpec, int widthMeasureSpec, Int heightMeasureSpec) {// Final int size = mChildrenCount; final View[] children = mChildren; View for (int I = 0; i < size; ++i) { final View child = children[i]; // Call measureChild() for the next measurement -> Analyze 2 measureChild(Child, widthMeasureSpec, heightMeasureSpec); } /** * Analysis 2: measureChild() * Effect: 1. Measure the width/height of each child View: Measure () */ protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) { // 1. Final LayoutParams lp = child.getLayoutParams(); final LayoutParams lp = child.getLayoutParams(); // 2. According to the parent view's MeasureSpec & LayoutParams, Calculate a single child View MeasureSpec final int childWidthMeasureSpec = getChildMeasureSpec (parentWidthMeasureSpec mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);  Measure () = measure(); // Measure () = measure(); Child. measure(childWidthMeasureSpec, childHeightMeasureSpec); }Copy the code

At this point, measure process analysis of the ViewGroup is complete


The process to summarize

Each method description of the measurement process (Measure) of the ViewGroup is summarized as follows.

In order to better understand the ViewGroup’s measure process (especially the onMeasure() clone), I will use the subclass of ViewGroup LinearLayout to analyze the ViewGroup’s measure process

Instance analysis

To better understand the ViewGroup’s measure process (especially the onMeasure() clone), this section will use a subclass of ViewGroup LinearLayout to analyze the ViewGroup’s measure process.

The onMeasure() of the LinearLayout is mainly analyzed here, as shown below.

Protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { MeasureVertical () -> 1 if (mOrientation == VERTICAL) {measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); } /** * Analysis 1: measureVertical() * Measure the vertical dimension of the LinearLayout */ measureVertical(int widthMeasureSpec, Final int count = getVirtualChildCount(); For (int I = 0; for (int I = 0; i < count; ++i) { final View child = getVirtualChildAt(i); GetChildrenSkipCount () returns 0 INVISIBLE if (child.getVisibility() == view.gone) {I += getChildrenSkipCount(child, I); continue; } // Measure totalWeight += lp.weight; // Measure totalWeight += lp.weight; if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) { // If the specMode of the LinearLayout is EXACTLY and the subview is set to weight, the measure process of the subview will be skipped and the skippedMeasure attribute will be marked as true. // If the sub-view of the LinearLayout is set with weight, the sub-view of the LinearLayout will perform two measure calculations, which is time-consuming. // That is why the sub-view of the LinearLayout needs to use weight. RelativeLayout final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); skippedMeasure = true; } else { int oldHeight = Integer.MIN_VALUE; // Step 1: The method ends up calling measureChildren() internally, To iterate through all the child View & measuring measureChildBeforeLayout (child, I, widthMeasureSpec, 0, heightMeasureSpec, totalWeight = = 0? mTotalLength : 0); . Final int childHeight = child.getMeasuredHeight(); // 1. MTotalLength Used to store the vertical height of the LinearLayout Final int totalLength = mTotalLength; // 2. Measure the height of each subview, MTotalLength = math. Max (totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); MTotalLength += mPaddingTop + mPaddingBottom; mTotalLength += mPaddingTop + mPaddingBottom int heightSize = mTotalLength; SetMeasureDimension (resolveSizeAndState(maxWidth,width)) setMeasureDimension(resolveSizeAndState(maxWidth,width)) }Copy the code

At this point, the analysis of the most important and complex measurement process (measure) in the custom View process is completed.


4. To summarize

  • Measures can be divided into two types according to the type of views: single View and ViewGroup;
  • The biggest difference between them is that a Measuer process of a single View implements onMeasure(), while a Measuer process of a ViewGroup does not.
  • The specific measurement process is summarized as follows


Welcome to Carson_HoRe the nuggets, please like the top/comment! Because your encouragement is the biggest power that I write!