1

I have a custom view that overrides onMeasure.

I see onMeasure() getting called twice, the first time with the heightMeasureSpec at 0x7fffffff - that doesn't seem to be a valid MeasureSpec value.

Then, it gets called again, that time with the proper value.

Here are the basic components, stripped down to the relevant parts:

The custom view:

public class CustomView extends ViewGroup implements SurfaceHolder.Callback {
    public CustomView(Context context, AttributeSet as) {
        super(context, as);

        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.custom_view, this, true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

        setMeasuredDimension(width, height);
    }
}

The layout (again, stripped down to keep this as brief as possible):

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    />

And here is the layout that contains it:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

        <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >        
            <CustomView
            android:id="@+id/custom_view"
            android:layout_width="@dimen/custom_size"
            android:layout_height="@dimen/custom_size"
            />
        </RelativeLayout>
    </ScrollView>
</LinearLayout>

Key observations here:

  • The custom view has a fixed width and height (the custom size is 128dp).
  • The view itself is inside a RelativeLayout.
  • All of this is inside a ScrollView.

In this scenario, I get onMeasure called first with 0x40000100, 0x7fffffff, and then again with 0x40000100, 0x40000100, which is what I would have expected in the first place.

Maybe it's perfectly normal for onMeasure to be called with an invalid height first? I could ignore it, but I don't want to add any hacks here.


ANSWER: It's a bug in the operating system that has been fixed, documented here:

In platform version 17 and lower, RelativeLayout was affected by a measurement bug that could cause child views to be measured with incorrect MeasureSpec values. (See MeasureSpec.makeMeasureSpec for more details.) This was triggered when a RelativeLayout container was placed in a scrolling container, such as a ScrollView or HorizontalScrollView. If a custom view not equipped to properly measure with the MeasureSpec mode UNSPECIFIED was placed in a RelativeLayout, this would silently work anyway as RelativeLayout would pass a very large AT_MOST MeasureSpec instead.

I'm not going to post this as an answer, Snicolas has been nice enough to look at this so he deserves his accepted answer.

EboMike
  • 76,846
  • 14
  • 164
  • 167
  • Hi @EboMike. Interesting stuff. Could you just try to remove one of the layouts inside the scroll view, just to make things more clear. Also, could you provide 2 data sets, one with WRAP_CONTENT and one with MATCH_PARENT for the parent layout of your custom view ? – Snicolas Nov 17 '13 at 06:57
  • Btw, your impl of onMeasure may create some troubles, did you have a look at http://stackoverflow.com/a/12267248/693752 ? – Snicolas Nov 17 '13 at 06:59
  • @Snicolas thanks for the comments - I experimented some more and was able to repro it in a much simpler setup - see the edited layout above. The RelativeLayout seems to be key, that causes the 0x7fffffff. I tried both wrap_content and match_parent, it does it in both cases. Also, I'm aware of the answer you linked, keep in mind I severely stripped down this example, the onMeasure() function is more complex than that. – EboMike Nov 17 '13 at 07:22
  • I don't think you should resolve the size when measurespec mode is exactly. Only when unspecified or at most. In all my implementations, I only apply a size it those modes. – Snicolas Nov 17 '13 at 07:46
  • I don't, but that's beyond the point - why is the measurespec passed in 0x7fffffff? That's not a valid value. – EboMike Nov 17 '13 at 07:55
  • Why do you take of values that are passed to onMeasure during intermediate passes and that you should not focus on but only resend to the layout framework directly ? I mean, it is not supposed to have a meaning for developers. It looks like the UI Layer of Android is trying to stretch the view to a maximum, but even then I am pretty sure that this value is discarded a few methods after your code is invoked, just like a first pass param that only has its full meaning in a subsequent pass. – Snicolas Nov 17 '13 at 08:02
  • My question is - onMeasure() is being called, and on the first call it's given what I suppose is an invalid value, so my question begins before I even handle onMeasure(). I'm overriding onMeasure(), so I'm supposed to do something with this value (I can't even pass it to a super class, nor do I want to). If it "has no meaning", how can I tell that it has no meaning? I'm trying to do some important things here (this view contains a camera preview, so I need to find a good size for the preview), so I'm relying on good values here. – EboMike Nov 17 '13 at 16:45

1 Answers1

2

I used this small IDE one gist. And, as you can see, both values you received are in the mode EXACTLY. If we follow references implemenations of onMeasure most favored on StackOverFlow, and they both go in the same direction :

there is nothing else you have to do except passing them to setMeasureDimensions. You can only modify the measures received in the case they are not in the mode EXACTLY.

Am I missing something ?

Community
  • 1
  • 1
Snicolas
  • 37,840
  • 15
  • 114
  • 173
  • 1
    You know what, it does make sense. The value just seemed flat out wrong to me, but it works if you think about the layout process. By the way, `resolveSize()` already handles it correctly - check the Android source, it honors EXACTLY the way it should. In any case, you're right - thinks are behaving the way they should, I just needed to understand that the layout process involves first stretching the view and then bringing it down to its proper size. Weird, since I already gave it a fixed size, but who cares. – EboMike Nov 17 '13 at 19:10
  • Turns out it's a bug in the operating system after all! See the documentation for RelativeLayout. They even kept the bug for all apps that don't target at least version 18. Still, it works, so I'm fine with it. – EboMike Nov 18 '13 at 17:48
  • Any link ? Thanks for the follow-up. – Snicolas Nov 18 '13 at 17:49
  • 1
    http://developer.android.com/reference/android/widget/RelativeLayout.html "In platform version 17 and lower, RelativeLayout was affected by a measurement bug that could cause child views to be measured with incorrect MeasureSpec values. [...] This was triggered when a RelativeLayout container was placed in a scrolling container, such as a ScrollView or HorizontalScrollView. If a custom view not equipped to properly measure with the MeasureSpec mode UNSPECIFIED was placed in a RelativeLayout, this would silently work anyway as RelativeLayout would pass a very large AT_MOST MeasureSpec instead." – EboMike Nov 18 '13 at 18:04
  • 1
    Lol, that's pretty much your case. Didn't they tell your name and link that thread as well ? Worth knowing, thx ;) – Snicolas Nov 18 '13 at 18:08