327

I tried to do custom component. I extended View class and do some drawing in onDraw overrided method. Why I need to override onMeasure? If I didn't, everything seen to be right. May someone explain it? How should I write my onMeasure method? I've seen couple tutorials, but each one is a little bit different than the other. Sometimes they call super.onMeasure at the end, sometimes they use setMeasuredDimension and didn't call it. Where is a difference?

After all I want to use several exactly the same components. I added those components to my XML file, but I don't know how big they should be. I want to set its position and size later (why I need to set size in onMeasure if in onDraw when I draw it, is working as well) in custom component class. When exactly I need to do that?

sennin
  • 7,364
  • 9
  • 30
  • 44

3 Answers3

760

onMeasure() is your opportunity to tell Android how big you want your custom view to be dependent the layout constraints provided by the parent; it is also your custom view's opportunity to learn what those layout constraints are (in case you want to behave differently in a match_parent situation than a wrap_content situation). These constraints are packaged up into the MeasureSpec values that are passed into the method. Here is a rough correlation of the mode values:

  • EXACTLY means the layout_width or layout_height value was set to a specific value. You should probably make your view this size. This can also get triggered when match_parent is used, to set the size exactly to the parent view (this is layout dependent in the framework).
  • AT_MOST typically means the layout_width or layout_height value was set to match_parent or wrap_content where a maximum size is needed (this is layout dependent in the framework), and the size of the parent dimension is the value. You should not be any larger than this size.
  • UNSPECIFIED typically means the layout_width or layout_height value was set to wrap_content with no restrictions. You can be whatever size you would like. Some layouts also use this callback to figure out your desired size before determine what specs to actually pass you again in a second measure request.

The contract that exists with onMeasure() is that setMeasuredDimension() MUST be called at the end with the size you would like the view to be. This method is called by all the framework implementations, including the default implementation found in View, which is why it is safe to call super instead if that fits your use case.

Granted, because the framework does apply a default implementation, it may not be necessary for you to override this method, but you may see clipping in cases where the view space is smaller than your content if you do not, and if you lay out your custom view with wrap_content in both directions, your view may not show up at all because the framework doesn't know how large it is!

Generally, if you are overriding View and not another existing widget, it is probably a good idea to provide an implementation, even if it is as simple as something like this:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int desiredWidth = 100;
    int desiredHeight = 100;

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;
    int height;

    //Measure Width
    if (widthMode == MeasureSpec.EXACTLY) {
        //Must be this size
        width = widthSize;
    } else if (widthMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        width = Math.min(desiredWidth, widthSize);
    } else {
        //Be whatever you want
        width = desiredWidth;
    }

    //Measure Height
    if (heightMode == MeasureSpec.EXACTLY) {
        //Must be this size
        height = heightSize;
    } else if (heightMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        height = Math.min(desiredHeight, heightSize);
    } else {
        //Be whatever you want
        height = desiredHeight;
    }

    //MUST CALL THIS
    setMeasuredDimension(width, height);
}

Hope that Helps.

devunwired
  • 60,762
  • 12
  • 121
  • 136
  • 1
    Hey @Devunwired nice explanation the best I read so far. Your explanation answered a lot of questions I had and cleared some doubts, but still one remains which is : If my custom view is inside a ViewGroup alongside with some others Views (doesn't matter which types) that ViewGroup will get all his children an for each one probe for their LayoutParams constraint and ask each child to measure it self accordingly to their constraints? – pharaoh Sep 14 '12 at 02:32
  • Yes, that is what the `measureChildren()` method of `ViewGroup` does during the measure/layout process. – devunwired Sep 14 '12 at 14:58
  • 50
    Note that this code won't do if you override onMeasure of any ViewGroup subclass. Your subviews won't show up and will all have a size of 0x0. If you need to override onMeasure of a custom ViewGroup, change widthMode, widthSize, heightMode and heightSize, compile them back to measureSpecs using MeasureSpec.makeMeasureSpec and pass resulting integers to super.onMeasure. – Alexey Jul 19 '13 at 09:19
  • @Alexey Yes, if you are customizing a `ViewGroup` you are also responsible for measuring the child views. You _could_ just call super, but you're more likely better off calling `measureChildren()` or measuring each child individually if that better fits the application. – devunwired Jul 20 '13 at 00:29
  • Gave this a try and noticed that if you set your size of your customView to wrap_content, it does actually specify a value so it never hits the MeasureSpec.UNSPECIFIED – Jonathan Aug 09 '13 at 16:15
  • Indeed one should never think that there is hard link between layout params and MeasureSpec, but instead use the guides of "use this size", "no larger than", or "be what you want" instead. May `ViewGroup` types do not allow children to be any larger than themselves, but others do. In cases where the layout constrains the child, `UNSPECIFIED` by itself is uncommon. – devunwired Aug 09 '13 at 18:35
  • 1
    Fantastic answer. Note that, as per Google's documentation, it's the View's responsibility to handle padding. – jonstaff Mar 06 '14 at 21:41
  • Hi Devunwired, I am little bit confused actually, I am overriding both onMeasure() and onDraw() and in onDraw() I am doing some view drawing stuff and there only I will be able to calculate the dimensions of Canvas. so, until that I don't know the size of view. So, how to set that canvas size as View's size because the onMeasure() is called before onDraw(). Thanks in advance – mithil1501 Feb 09 '17 at 09:36
  • 4
    Over complicated c**p that makes Android a painful layout system to work with. They could have just had getParent().get***()... – Oliver Dixon Apr 20 '17 at 08:10
  • 2
    There are helper methods in the `View` class, called `resolveSizeAndState` and `resolveSize`, that should do what the 'if' clauses do - I found them useful, especially if you have to write those IFs often. – stan0 Aug 09 '17 at 13:00
  • @OliverDixon, sdk developers have, apparently, slacked their whole work off )) – Farid Jul 23 '19 at 09:26
5

actually, your answer is not complete as the values also depend on the wrapping container. In case of relative or linear layouts, the values behave like this:

  • EXACTLY match_parent is EXACTLY + size of the parent
  • AT_MOST wrap_content results in an AT_MOST MeasureSpec
  • UNSPECIFIED never triggered

In case of an horizontal scroll view, your code will work.

Florian
  • 1,928
  • 1
  • 24
  • 36
  • 57
    If you feel that some answer here is incomplete, please add to it rather than give a partial answer. – Michaël Nov 01 '13 at 14:49
  • 1
    Good onya for linking this to how layouts work, but in my case onMeasure is called three times for my custom view. The view in question had a wrap_content height and a weighted width (width = 0, weight = 1). The first call had UNSPECIFIED / UNSPECIFIED, the second had AT_MOST / EXACTLY and the third had EXACTLY / EXACTLY. – William T. Mallard Jul 02 '17 at 02:46
2

If you don't need to change something onMeasure - there's absolutely no need for you to override it.

Devunwired code (the selected and most voted answer here) is almost identical to what the SDK implementation already does for you (and I checked - it had done that since 2009).

You can check the onMeasure method here :

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) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

Overriding SDK code to be replaced with the exact same code makes no sense.

This official doc's piece that claims "the default onMeasure() will always set a size of 100x100" - is wrong.

Maverick Meerkat
  • 3,847
  • 2
  • 31
  • 49