preface

Currently, the source code of the View measurement process is explored in this article, but it still has UNSPECIFIED values. At the end of the article, I leave UNSPECIFIED values as MeasureSpec. So TODAY I simply share the answer with you, by the way, we also put the phenomenon and the cause of the stroke again.

review

To remind you of the questions left, I give you two pieces of code:

Scenario 1.


      
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

   <View
       android:layout_width="360dp"
       android:layout_height="400dp"
       android:layout_gravity="center"
       android:background="@color/red" />

</ScrollView>
Copy the code

Scenario 2.


      
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

   <FrameLayout
       android:layout_width="wrap_content"
       android:layout_height="wrap_content">
      
      <View
          android:layout_width="360dp"
          android:layout_height="400dp"
          android:layout_gravity="center"
          android:background="@color/red" />
      
   </FrameLayout>
   
</ScrollView>
Copy the code

Scene 1 The display effect is as follows:

The display effect of scene 2 is as follows:

Yes, you read that correctly, in scene ①, we set 400dp to View did not work, display blank page, in scene ② normal display. So what’s going on? The quickest way to find the cause of the problem is to find the answer in the source code.

Let’s start with the ScrollView class:

ScrollView

//ScrollView.java
/ / 1
public class ScrollView extends FrameLayout {...@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        / / 2
        super.onMeasure(widthMeasureSpec, heightMeasureSpec); . }}Copy the code

Note 1 shows that ScrollView inherits from FrameLayout,

The onMeasure() method at annotation 2 is also the onMeasure() method of the FrameLayout call.

WidthMeasureSpec and heightMeasureSpec FrameLayout measures each child as follows:

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

  	/ / 1
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    / / 2
    final int childHeightMeasureSpec=getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code

Childchildmeasurespec () = childChildMeasurespec (); childChildMeasurespec () = ChildChildChildRespec ();

If a FrameLayout contains a View with a fixed width and height, WidthMeasureSpec (size=360dp,mode=EXACTLY) heightMeasureSpec(size=400dp,mode=EXACTLY) widthMeasureSpec(size=360dp,mode=EXACTLY) And then pass the two measureSpec to View measure method of measurement results is supposed to come out the View is wide high 360 dp respectively and 400 dp ah, why didn’t show up?

Don’t worry, we can check out all the ScrollView methods in Android Studio to see if there are any bugs:

I’ll go.. ScrollView overrides the measureChildWithMargins method. Click here to find out:

@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    / / 1
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed;
    / / 2
    final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
            Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
            MeasureSpec.UNSPECIFIED);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code
  • Annotation 1 is nothing special, normally build childWidthMeasureSpec
  • UNSPECIFIED, the ScrollView assigns mode as MeasureSpec.UNSPECIFIED when building childHeightMeasureSpec

UNSPECIFIED mode is set as MeasureSpec.UNSPECIFIED mode and passed to the child View. The child View calculates its size in this mode.

//View.java

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
      / / 1
      case MeasureSpec.UNSPECIFIED:
        result = size;
        break; . }return result;
}
Copy the code

UNSPECIFIED Mode, heightMeasureSpec returns the size of The parent.UNSPECIFIED mode.

protected int getSuggestedMinimumHeight(a) {
    return (mBackground == null)? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }//Drawable.java
public int getMinimumHeight(a) {
    final int intrinsicHeight = getIntrinsicHeight();
    return intrinsicHeight > 0 ? intrinsicHeight : 0;
}
Copy the code

The above code is the value of size: takes the larger values of the MIN_height and background Drawable IntrinsicHeight set by the XML. Because we didn’t set properties related to XML So getSuggestedMinimumHeight () method returns 0, so we see a scene (1) the height of the View is actually a 0, shows blank. The scene ② shows the normal reason should be very easy to find, directly view the relevant source code FramLayout:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {.../ / 1
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
}

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
     final int specMode = MeasureSpec.getMode(measureSpec);
     final int specSize = MeasureSpec.getSize(measureSpec);
     final int result;
     switch (specMode) {
       ...
       / / 2
       case MeasureSpec.UNSPECIFIED:
       default:
         result = size;
     }
     return result | (childMeasuredState & MEASURED_STATE_MASK);
}                   
Copy the code
  • Annotation 1 calls the resolveSizeAndState method when the FrameLayout size is set
  • The resolveSizeAndState method returns the maximum height of all subviews/viewgroups. The maxHeight is the maximum height of FrameLayout. So the maxHeight is 400dp, so scene 2 will display normally.

The above a few code introduced scenario 1 and scenario (2) the reason clearly, so ScrollView/NestedScrollView why do it?

The root cause

After the ScrollView/NestedScrollView relevant source, my personal understanding of reason is:

ScrollView sliding is achieved by scrollBy, but sliding must have a range, the calculation rule of the range is to get the true height of the child view after subtracting the height of the ScrollView, you can get the range of the ScrollY. With normal measurement mode, the child view is at most the same size as the parent view, and the true width and height cannot be retrieved. As a result, the measurement mode is revealed. Many of the Controls provided by Android, FrameLayout, LinearLayout, etc. can measure the true height in this measurement mode. When setting the width and height of the ViewGroup (setMeasuredDimension), MeasureSpec = measureSpec = measureSpec = measureSpec = measureSpec = measureSpec = measureSpec = measureSpec = measureSpec;

NestedScrollView Wrap RecyclerView pit

After talking about root causes, here’s a guide to avoiding pits: Before I have seen a lot of students in a slightly complicated list layout, will use NestedScrollView wrapped RecyclerView to realize some of the top add head layout, from the above reason analysis we can know that RecyclerView in this case will also directly measure the real height, This will cause all the RecyclerView items directly all go onBindViewholder() method, RecyclerView reuse mechanism failure, if the amount of data when the page will be very card… Interested students can print log view or RecyclerView related source code.

conclusion

Try not to use NestedScrollView parcel RecyclerView, try not to use NestedScrollView parcel RecyclerView, try not to use NestedScrollView parcel RecyclerView important words say three times, If the list is complex, it is recommended to use RecyclerView multi-item type or MergeAdapter and so on. If you have no program, come to me and I will provide you with more programs, hahaha 😂…

The Github address is as follows: github.com/hyy920109/H…

supplement

There is a problem with NestedScrollView nested RecyclerView, ScrollView nested RecyclerView is no problem (but there is a sliding conflict problem), and finally check the reason: NestedScrollView and ScrollView override measureChildWithMargins, but the rules are slightly different, and nesting is not recommended as a whole. This topic stops here, my conclusion is only as a reference, mainly want to let everyone familiar with the measurement process, you can try to write viewGroup, will deepen the impression.