2

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;              
            }
        }
    }
}
Sound Conception
  • 5,263
  • 5
  • 30
  • 47
  • post MyCustomViewGroup.onLayout – pskink Jan 04 '14 at 10:05
  • @pskink Hi again. Thanks for having a look. I've updated my post as requested. – Sound Conception Jan 04 '14 at 10:24
  • sorry, but cannot help much since i see the opposite: wirhout params i see nothing, with params wrap_content, weap_content i see something – pskink Jan 04 '14 at 10:52
  • but in general, shouldnt you measure() all of your children in onMeasure? – pskink Jan 04 '14 at 11:21
  • @pskink Yes I should and I do. This ViewGroup is eventually going to have numerous other child views added to it in other locations. However, to start with I'm just working on the header bar which will have several ImageButtons and a TextView in a single line across the top of the ViewGroup. But to avoid any confusion, I've now written a little test app which does not have this complication. I'll post this in place of the old and misleading code. – Sound Conception Jan 04 '14 at 12:04
  • ok, i see, but do you really need you cusrom ViewGroup?, what children you wanna place thete, what about exisring *Latouts? – pskink Jan 04 '14 at 12:27
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/44495/discussion-between-sound-conception-and-pskink) – Sound Conception Jan 04 '14 at 12:30
  • you forgot measureChild(child, widthMeasureSpec, heightMeasureSpec); when calculating the height in onMeasure – pskink Jan 04 '14 at 13:51
  • @pskink Thanks! I've been banging my head against this for literally hours. Looks like it got to the point where I "couldn't see for looking" as they say. I see you already have lots of Rep, but I'd gladly accept and upvote if you want to put that in an answer. – Sound Conception Jan 04 '14 at 14:03

1 Answers1

8

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

pskink
  • 23,874
  • 6
  • 66
  • 77