android – Views inside a custom ViewGroup not rendering after a size change-ThrowExceptions

Exception or error:

I’m running into a problem that has me stumped and I was hoping someone could give me some pointers.

I’m working on an application that uses a custom ViewGroup (actually a FrameLayout that contains a RelativeLayout) to render an event calendar. The events inside the calendar are represented as views that are sized according to the duration of the event and the size of the containing view.

I’m running into a problem that occurs when the containing FrameLayout is resized. The current implementation removes all of the views that represent the events and attempts to add new ones and calculate their sizes based on the current size of the FrameLayout. This work is being triggered via the onSizeChanged() method of View which is overridden in the FrameLayout.

When the view is resized, this code is executed and the views are updated, however, none of them actually render on the screen… the views contained within the FrameLayout are simply not visible. If I load the view in the hierarchyviewer tool, they are part of the view tree, and are outlined in the overview in the positions they should be in, but they do not display. (Note that the views are visible on the initial render of the FrameLayout… it’s only after a resize that they disappear.)

It appears that the order of events during the resize is as follows:

onMeasure()
onMeasure()
onSizeChanged()
onLayout()

Calling requestLayout() after resetting the views (inside onSizeChanged()) seems to have no effect. However, if I cause some delay before calling requestLayout(), the views become visible. I can cause this delay by spawning a thread and sleeping, or by creating a dummy button that simply calls requestLayout() and pressing it after the resize myself, or even this ugly hack placed at the end of onSizeChanged():

post(new Runnable() {
    public void run() {
        requestLayout();
    }
});

When I use this hack, the contained views are visible, and the order of events is as follows:

onMeasure()
onMeasure()
onSizeChanged()
onLayout()
onMeasure()
onMeasure()
onLayout()

So it appears that forcing a second measure pass (after the view tree has been modified) makes the contained views visible as they should be. Why delaying the call to requestLayout() does this is a mystery to me.

Can anyone provide any pointers as to what I’m doing wrong?

I realize that this is somewhat difficult to follow without looking at some code, so I have created a small sample application that exhibits my issue and made it available on Github:

https://github.com/MichaelSims/ViewGroupResizeTest

The hack I mentioned above is committed to a separate branch:

https://github.com/MichaelSims/ViewGroupResizeTest/tree/post-runnable-hack

If I can provide any additional information, please let me know and thanks in advance.

How to solve:

It’s not a hack, it’s how it’s supposed to work. onSizeChanged() is invoked as part of the layout pass, and you cannot/should not requestLayout() during a layout pass. It is correct to post a requestLayout() on the event queue to indicate that you have changed the View hierarchy during a layout pass.

###

I run into the same problem.

My onMeasure() was something like this:

@Override    
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
        //calculate new width and height here
        ...
        setMeasuredDimension(newMeasureSpecWidth, newMeasureSpecHeight);
    }

My mistake was that I was calling super.onMeasure() on the first line of onMeasure(), so the inner children of my ViewGroup were being calculated based on a size that was about to change.

So my fix was doing something like this:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    //calculate new width and height here
    ...
    int newMeasureSpecWidth = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY);
    int newMeasureSpecHeight = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY);
    super.onMeasure(newMeasureSpecWidth, newMeasureSpecHeight);
}

So I was setting the new size to my ViewGroup using the call to super.onMeasure(), and then it is also communicating the new size to its children.

Remember: the contract when you override onMeasure() is that you have to call setMeasuredDimension() (and you can achieve this by calling the method itself or by calling super.onMeasure())

Leave a Reply

Your email address will not be published. Required fields are marked *