When is View.onMeasure() called?
Asked Answered
P

2

31

When is

View.onMeasure(int widthMeasureSpec, int heightMeasureSpec)

called? I have an Activity that needs to perform an action after onMeasure has been called.

My question is the same as the unanswered question posted here. The View documentation states that onMeasure is called when requestLayout() is called, which is apparently called by a view on itself when it believes that is can no longer fit within its current bounds.

However, this doesn't tell me when my activity can assume that my View has been measured. I've used this code to extend ImageView to form TouchImageView. It was suggested here that I should use the onMeasure method to scale my image. I wish to update the value of a TextView after the ImageView has been measured in order to display the percentage by which the image has been scaled.

Pub answered 8/7, 2011 at 22:12 Comment(3)
Because I only have 10 points, I'm not able to add more than two links, even though they're pointing to domains like stackoverflow.com and developer.android.com. Thank you for fixing my links, but you unnecessarily removed my quotation. I can't fix it without removing the links, due to my limitation.Pub
Sorry, I don't see what quotation I removed. If you tell me, I can put it back...Phonology
"called by a view on itself when it believes that is can no longer fit within its current bounds" was taken verbatim from the View documentation. It has a typo in it.Pub
P
20

onMeasure is called when the parent View needs to calculate the layout. Typically, onMeasure may be called several times depending on the different children present and their layout parameters.

The best way to do something when onMeasure is called is (I think) to create your own control, extend the ImageView and override onMeasure (just call super.onMeasure and do whatever else you would like to do).

If you do that, just keep in mind that on Measure may be called several times with different parameters, so whatever is measured may not be the final width and height of what will be actually displayed.

Phonology answered 8/7, 2011 at 22:19 Comment(3)
Thank you for answering my question, but I believe you missed my point. As I say in my question, I did extend ImageView and I did Override the onMeasure method. However, there is an object that belongs to my activity (a TextView) that needs to be updated after onMeasure has been called.Pub
Just add a function setAssociatedTextView(TextView tv) in your class that extends ImageView and use that in your onMeasure function, not sure what the problem is... ? But again, there is not really anyway for you to tell when the view is measured entirely (since onMeasure would most likely be called several times)Phonology
That fixed my problem, but it feels dirty. I'm sure I must be violating some sort of design principle best practice to do with dependency. I had to copy code from my Activity into my TouchImageView (ImageView extension) and now the Activity depends on the views and one of the views depends on the other one. It works, though, so thanks for the solution.Pub
G
39

You can get your custom view's measurements in onSizeChanged.

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);

    // use the provided width and height for whatever you need
}

Explanation

When a view is created, this is the order in which the following methods are called:

  1. Constructor
    • CustomView(Context context) (if created programmatically)
    • CustomView(Context context, AttributeSet attrs) (if created from xml)
  2. onFinishInflate (assuming you used the xml constructor)
  3. onAttachedToWindow
  4. onMeasure
  5. onSizeChanged
  6. onLayout
  7. onDraw

The earliest you can get the view's measurements is in onMeasure. Before that the width and height are 0. However, the only thing you should be doing in onMeasure is determining the size of the view. This method gets called several times while the view is telling the parent how big it wants to be but the parent is determining the actual final size. (See this answer for how onMeasure is meant to be used.)

If you want to actually use the measured size for anything, the earliest place to do that is in onSizeChanged. It gets called whenever the view is created because the size is changing from 0 to whatever the size is.

You could also use onLayout, though as I understand it, onLayout is for customizing how any children of your custom view are laid out. It also might get called more often than onSizeChanged, for example, if you call requestLayout() when the size hasn't actually changed.

You can also access the size in onDraw with getMeasuredWidth() and getMeasuredHeight(). However, if you are using them to do any heavy calculations, it is better to do that beforehand. Generally speaking, try to keep as much out of onDraw as possible since it may be called multiple times. (It gets called whenever invalidate() gets called.)

See for yourself

If you don't believe me, you can see the order of events as they are called in the custom view below. Here is the output:

XML constructor called, measured size: (0, 0)
onFinishInflate called, measured size: (0, 0)
onAttachedToWindow called, measured size: (0, 0)
onMeasure called, measured size: (350, 1859)
onMeasure called, measured size: (350, 350)
onMeasure called, measured size: (350, 2112)
onMeasure called, measured size: (350, 350)
onSizeChanged called, measured size: (350, 350)
onLayout called, measured size: (350, 350)
onDraw called, measured size: (350, 350)

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.viewlifecycle.CustomView
        android:id="@+id/customView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/colorAccent"/>

</RelativeLayout>

CustomView.java

public class CustomView extends View {

    private void printLogInfo(String methodName) {
        Log.i("TAG", methodName + " called, measured size: (" + getMeasuredWidth() + ", " + getMeasuredHeight() + ")");
    }

    // constructors

    public CustomView(Context context) {
        super(context);
        printLogInfo("Programmatic constructor");
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        printLogInfo("XML constructor");
    }

    // lifecycle methods

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        printLogInfo("onFinishInflate");
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        printLogInfo("onAttachedToWindow");
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        printLogInfo("onMeasure");
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        printLogInfo("onSizeChanged");
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        printLogInfo("onLayout");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        printLogInfo("onDraw");
    }
}

Further reading

Gemoets answered 15/2, 2017 at 3:53 Comment(3)
by Constructor do you mean onCreate?Unformed
@dankal444, no, a custom view doesn't have an onCreate method. The constructor is the method you use to create a new view object, though. In my CustomView.java example above, there are two constructor methods: CustomView(Context context) and CustomView(Context context, AttributeSet attrs).Gemoets
I'm stumped. If onLayout() is where the child views are laid out then a ViewGroup that has its height determined by the layout of the child views won't have that height until after onMeasure() has already been called. It seems like the layout of child views, if they affect the height of the ViewGroup, must be done prior to onMeasure being called (or in the onMeasure() function itself). The order of these calls seems out-of-whack because of this layout-changes-height-after_measure-is-done issue.Inerrable
P
20

onMeasure is called when the parent View needs to calculate the layout. Typically, onMeasure may be called several times depending on the different children present and their layout parameters.

The best way to do something when onMeasure is called is (I think) to create your own control, extend the ImageView and override onMeasure (just call super.onMeasure and do whatever else you would like to do).

If you do that, just keep in mind that on Measure may be called several times with different parameters, so whatever is measured may not be the final width and height of what will be actually displayed.

Phonology answered 8/7, 2011 at 22:19 Comment(3)
Thank you for answering my question, but I believe you missed my point. As I say in my question, I did extend ImageView and I did Override the onMeasure method. However, there is an object that belongs to my activity (a TextView) that needs to be updated after onMeasure has been called.Pub
Just add a function setAssociatedTextView(TextView tv) in your class that extends ImageView and use that in your onMeasure function, not sure what the problem is... ? But again, there is not really anyway for you to tell when the view is measured entirely (since onMeasure would most likely be called several times)Phonology
That fixed my problem, but it feels dirty. I'm sure I must be violating some sort of design principle best practice to do with dependency. I had to copy code from my Activity into my TouchImageView (ImageView extension) and now the Activity depends on the views and one of the views depends on the other one. It works, though, so thanks for the solution.Pub

© 2022 - 2024 — McMap. All rights reserved.