Skip to content

Measuring

ZieIony edited this page Oct 24, 2019 · 2 revisions

If the custom view adds something to its content, you probably have to override measuring. It's pretty easy for simple views and more difficult for layouts. Compound views usually don't need custom measuring as their base layouts can handle most of the cases.

Measuring is done in View.onMeasure(). This method has two parameters - measure specifiers for width and height. Both of these values contain a mode and a size. You can extract them using helper methods, like this:

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

These values come from layout params and view's parents. Modes can take one of three values:

  • MeasureSpec.EXACTLY - the view has its dimension specified using an exact value, for example: android:layout_width="100dp". The value is in the size part of the spec parameter.
  • MeasureSpec.AT_MOST - the view has its dimension restricted to its parent's size. The parent's size is in the size part of the spec parameter.
  • MeasureSpec.UNSPECIFIED - the view uses wrap_content for this parameter and you have to measure it.

Measure specifiers are the most important thing, but there are others. When measuring, you need to remember about padding values and minimal sizes. You can get paddings using View.getPadding[Left|Right|Top|Bottom]() and minimal sizes using getSuggestedMinimum[Width|Height]().

At the end of onMeasure() you have to call setMeasuredDimension() to set the values. This is only a suggestion for the view's parent, because, for example, there can be not enough space to lay the view out with the measured sizes. There are two sets of methods to get sizes - getMeasured[Width|Height]() (gets measured values) and get[Width|Height]() (gets sizes after layout().

The most interesting question now is how to measure. There's no good answer because each view has its own specific content you have to measure. For example, if you display a bitmap image, measuring is very easy because you can just use the image's width and height. If you display a vector image, it's completely up to you, how you'd like to measure in that situation because vectors don't have an intrinsic size.

Here's a template you can use for your onMeasure():

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    int width = getPaddingLeft() + getPaddingRight();
    int height = getPaddingTop() + getPaddingBottom();

    if (widthMode == MeasureSpec.EXACTLY) {
        width = widthSize;
    } else {
        width = // here really measure the view

        width = Math.max(width, getSuggestedMinimumWidth());
        if (widthMode == MeasureSpec.AT_MOST)
            width = Math.min(widthSize, width);
    }

    if (heightMode == MeasureSpec.EXACTLY) {
        height = heightSize;
    } else {
        height = // here really measure the view when its width is known

        height = Math.max(height, getSuggestedMinimumHeight());
        if (heightMode == MeasureSpec.AT_MOST)
            height = Math.min(height, heightSize);
    }

    setMeasuredDimension(width, height);
}