Search code examples
androidviewgrouponmeasure

ViewGroup child heights measure 0 when parent set to WRAP_CONTENT


I am creating a custom ViewGroup. If I add my custom ViewGroup to a layout without specifying an LayoutParams (as shown below), it displays correctly:

...
MyCustomViewGroup myViewGroup = new MyCustomViewGroup(this);
myRelativeLayout.addView(myViewGroup);
...

If I specify LayoutParams and set the width to MATCH_PARENT and height to WRAP_CONTENT (as shown below), it does not display:

...
MyCustomViewGroup myViewGroup = new MyCustomViewGroup(this);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
        RelativeLayout.LayoutParams.MATCH_PARENT,
        RelativeLayout.LayoutParams.WRAP_CONTENT);
myRelativeLayout.addView(myViewGroup, params);
...

I've run Debug for both scenarios.

Scenario 1
If I don't specify any LayoutParams, the child views measure correctly, and when the method iterates through all the child views to determine the maximum height, child.getMeasuredHeight() returns correct values each time.

Scenario 2
If I specify LayoutParams with width as MATCH_PARENT, and height as WRAP_CONTENT, the system does two passes through onMeasure as described below.

Pass 1
widthSpecMode = EXACTLY
width = widthSpecSize = 758 which is the width of the parent RelativeLayout
heightSpecMode = EXACTLY
height = heightSpecSize = 1055 which is the height of the parent RelativeLayout

Pass 2
widthSpecMode = EXACTLY
width = widthSpecSize = 758 which is the width of the parent RelativeLayout
heightSpecMode = AT_MOST
The method then iterates through all the child views to determine the maximum height, but child.getMeasuredHeight() returns 0 each time.

The child views are several ImageButtons and a TextView. They all have content, and that content is displayed correctly in the first scenario. Why does it come out as 0 height in the second scenario, and how can I fix it?

`' I have written a simplified test app to recreate the problem. All the necessary code is shown below. If anyone wishes to try it, you can simply cut and paste.

MainActivity.java

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RelativeLayout rootLayout = (RelativeLayout)findViewById(R.id.LayoutRoot);

        // Create an instance of MyViewGroup
        MyViewGroup viewGroupOne = new MyViewGroup(this);
        viewGroupOne.setId(1);
        rootLayout.addView(viewGroupOne);

        // Create a second instance and set layout width and height both to WRAP_CONTENT
        MyViewGroup viewGroupTwo = new MyViewGroup(this);
        viewGroupTwo.setId(2);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
                RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
        params.addRule(RelativeLayout.BELOW, 1);
        rootLayout.addView(viewGroupTwo, params);

        // Create a third Instance. Set layout width to MATCH_PARENT and height to WRAP_CONTENT
        MyViewGroup viewGroupThree = new MyViewGroup(this);
        params  = new RelativeLayout.LayoutParams(
                RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
        params.addRule(RelativeLayout.BELOW, 2);
        rootLayout.addView(viewGroupThree, params);     
    }
}

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LayoutRoot"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

</RelativeLayout>

MyViewGroup.java

public class MyViewGroup extends ViewGroup {

    private static int instanceCounter;

    public MyViewGroup(Context context) {
        super(context);

        instanceCounter++;

        // Add a TextView
        TextView textView = new TextView(context);
        String text = "Instance " + instanceCounter;
        textView.setText(text);
        addView(textView);

        // Add an ImageView
        ImageView imageView = new ImageView(context);
        imageView.setImageResource(android.R.drawable.ic_menu_add);
        addView(imageView);

    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int childCount = getChildCount();
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        int width = 0;
        int height = 0;
        int childState = 0;

        // Measure Width
        if (widthSpecMode == MeasureSpec.EXACTLY) {
            width = widthSpecSize;          
        } else {
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    measureChild(child, widthMeasureSpec, heightMeasureSpec);
                    width += child.getMeasuredWidth();
                }
            }
        }
        if (widthSpecMode == MeasureSpec.AT_MOST) {
            width = Math.min(width, widthSpecSize);
        }

        // Measure Height
        if (heightSpecMode == MeasureSpec.EXACTLY) {
            height = heightSpecSize;            
        } else {
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    height = Math.max(height, child.getMeasuredHeight());
                }
            }
        }
        if (heightSpecMode == MeasureSpec.AT_MOST) {
            height = Math.min(height, heightSpecSize);
        }

        // Combine child states
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                childState = combineMeasuredStates(childState, child.getMeasuredState());
            }
        }

        // Check against minimum width and height
        width = Math.max(width, getSuggestedMinimumWidth());
        height = Math.max(height, getSuggestedMinimumHeight());

        // Report final dimensions
        setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
                resolveSizeAndState(height, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int leftPos = 0;

        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            final int childWidth = child.getMeasuredWidth();

            if (child.getVisibility() != GONE) {
                child.layout(leftPos, 0, leftPos + childWidth, getMeasuredHeight());
                leftPos += childWidth;              
            }
        }
    }
}

Solution

  • ok soit seems you forgot to call measureChild(child, widthMeasureSpec, heightMeasureSpec); when calculating the height in onMeasure