141

I am trying to apply an animation to a view in my Android app after my activity is created. To do this, I need to determine the current size of the view, and then set up an animation to scale from the current size to the new size. This part must be done at runtime, since the view scales to different sizes depending on input from the user. My layout is defined in XML.

This seems like an easy task, and there are lots of SO questions regarding this though none which solved my problem, obviously. So perhaps I am missing something obvious. I get a handle to my view by:

ImageView myView = (ImageView) getWindow().findViewById(R.id.MyViewID);

This works fine, but when calling getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().width, etc., they all return 0. I have also tried manually calling measure() on the view followed by a call to getMeasuredWidth(), but that has no effect.

I have tried calling these methods and inspecting the object in the debugger in my activity's onCreate() and in onPostCreate(). How can I figure out the exact dimensions of this view at runtime?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Nik Reiman
  • 39,067
  • 29
  • 104
  • 160
  • 1
    Oh, also I should note that the view itself definitely does *not* have 0 width/height. It appears on the screen just fine. – Nik Reiman Sep 23 '10 at 14:03
  • Can you post tag from xml layout? – fhucho Sep 23 '10 at 15:41
  • 1
    I struggled with many SO solutions to this problem until I realized that in my case the dimensions of the View matched the physical screen (my app is "immersive" and the View and all of its parents' widths and heights are set to `match_parent`). In this case, a simpler solution that can safely be called even before the View is drawn (e.g., from your Activity's onCreate() method) is just to use Display.getSize(); see http://stackoverflow.com/a/30929599/5025060 for details. I know this isn't what @NikReiman asked for, just leaving a note to those who may be able to use this simpler method. – CODE-REaD Aug 16 '16 at 13:37

13 Answers13

196

Use the ViewTreeObserver on the View to wait for the first layout. Only after the first layout will getWidth()/getHeight()/getMeasuredWidth()/getMeasuredHeight() work.

ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
  viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
      view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
      viewWidth = view.getWidth();
      viewHeight = view.getHeight();
    }
  });
}
Vikram Bodicherla
  • 7,133
  • 4
  • 28
  • 34
81

There are actually multiple solutions, depending on the scenario:

  1. The safe method, will work just before drawing the view, after the layout phase has finished:
public static void runJustBeforeBeingDrawn(final View view, final Runnable runnable) {
    final OnPreDrawListener preDrawListener = new OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            view.getViewTreeObserver().removeOnPreDrawListener(this);
            runnable.run();
            return true;
        }
    };
    view.getViewTreeObserver().addOnPreDrawListener(preDrawListener); 
}

Sample usage:

    ViewUtil.runJustBeforeBeingDrawn(yourView, new Runnable() {
        @Override
        public void run() {
            //Here you can safely get the view size (use "getWidth" and "getHeight"), and do whatever you wish with it
        }
    });
  1. On some cases, it's enough to measure the size of the view manually:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

If you know the size of the container:

    val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxWidth, View.MeasureSpec.AT_MOST)
    val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST)
    view.measure(widthMeasureSpec, heightMeasureSpec)
    val width=view.measuredWidth
    val height=view.measuredHeight
  1. if you have a custom view that you've extended, you can get its size on the "onMeasure" method, but I think it works well only on some cases :
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    final int newHeight= MeasureSpec.getSize(heightMeasureSpec);
    final int newWidth= MeasureSpec.getSize(widthMeasureSpec);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
  1. If you write in Kotlin, you can use the next function, which behind the scenes works exactly like runJustBeforeBeingDrawn that I've written:

     view.doOnPreDraw { actionToBeTriggered() }
    

Note that you need to add this to gradle (found via here) :

android {
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

implementation 'androidx.core:core-ktx:#.#'
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • 1
    Seems that solution #1 will not always work with `OnPreDrawListener`. Tested case when you want to run code from Activity's `onCreate()` and you immediately background app. Easy to repeat especially on slower device. If you replace `OnPreDrawListener` with `OnGlobalLayoutListener` - you won't see same issue. – questioner Dec 14 '15 at 13:58
  • @questioner I don't understand, but I'm happy it helped you in the end. – android developer Dec 14 '15 at 14:22
  • Though I usually use `ViewTreeObserver` or `post`, in my case number 2 helped (`view.measure`). – CoolMind Feb 07 '18 at 16:56
  • 3
    @CoolMind You can also use a new method, in case you use Kotlin. Updated answer to include it too. – android developer Feb 07 '18 at 22:12
  • why does kotlin have additional functions for this? google are crazy, this language is useless (just should stay with java), it's much older than swift but only swift got enough popularity https://www.tiobe.com/tiobe-index/ (swift 12 position and kotlin 38) – user924 Mar 10 '18 at 01:16
  • @user924 "why does kotlin have additional functions for this?" - It's not in Kotlin framework. It's in an extra library that you are free to use. If you don't use it, it doesn't make the app smaller or larger, if you can always use Proguard. – android developer Mar 10 '18 at 11:30
  • @user924 "values could be incorrect, so don't use solution #2" - that's why I wrote "in some cases". It depends on what you do. – android developer Mar 10 '18 at 11:31
  • @user924 "why do you put some weird runnable in the first solution?" - as a convenience, and to avoid bugs. Also, BTW, speaking of bugs, you forgot to remove the listener, as I did. That's one reason to use it instead. – android developer Mar 10 '18 at 11:32
  • @user924 OK. I think in Kotlin it doesn't matter much, because of function-inline. So even if you use the shortened version, behind the scenes it will become the larger version, without any Runnable. Just as in the case of using `Math.max` , instead of the full condition. – android developer Mar 11 '18 at 08:10
  • @user924 Kotlin can be used on PC, as far as I remember. And even if it's not, you can put the logic in Kotlin, and still access it via Java. – android developer Mar 11 '18 at 18:04
  • @user25 On #1 , just like all operations on UI views, you will have it run on the UI thread anyway, in both the caller and then in on onPreDraw. No need for handler. – android developer Mar 23 '18 at 14:46
  • @user25 If there won't be a call to "run", nothing will occur. – android developer Mar 23 '18 at 14:46
  • @user25 Show me the exact issue, in a sample project. To me it worked in all cases. – android developer Mar 23 '18 at 14:47
  • @user25 "run" makes perfect sense. You pass the logic you want to do when you are ready to get the size of the view. You can't pass a function reference on Java, like on some languages. Instead you pass something that can run, like a Runnable. It is meant to be used multiple times, in various places in your app. – android developer Mar 23 '18 at 14:48
  • @user25 How is it related? Runnable is not a thread. It will run on the UI thread, as you are supposed to do. You are supposed to do UI operations on the UI thread. – android developer Mar 23 '18 at 14:50
  • @user25 You can't pass "method" to the function "runJustBeforeBeingDrawn" . You can pass only instances of a class. – android developer Mar 23 '18 at 14:50
  • @user25 Which is why it's perfectly fit to put it here. A thread isn't related to here. A runnable is the best thing. You just put the code in it , as you are promised to be able to get the size of the view there. Threads have nothing to do with here. It's just a Runnable. – android developer Mar 23 '18 at 14:52
  • @androiddeveloper I'm sorry I understood, it's meant to be as parameter, I just don't use it in different class – user25 Mar 23 '18 at 14:52
  • @androiddeveloper for me it didn't work sometimes when I got back from another activity – user25 Mar 23 '18 at 14:54
  • @user25 I've updated my answer to show you how to use it. Maybe now you will understand the logic of it. It's besically the same as what Google offers for Kotlin. – android developer Mar 23 '18 at 14:56
  • @user25 It depends on how you use it, of course. It's a one time usage. – android developer Mar 23 '18 at 14:57
  • @androiddeveloper no I understood how it works, so we can use it to get callback from another class (util class for example), I actually don't use such tricks, but now mb I'll try sometime – user25 Mar 23 '18 at 14:58
23

Are you calling getWidth() before the view is actually laid out on the screen?

A common mistake made by new Android developers is to use the width and height of a view inside its constructor. When a view’s constructor is called, Android doesn’t know yet how big the view will be, so the sizes are set to zero. The real sizes are calculated during the layout stage, which occurs after construction but before anything is drawn. You can use the onSizeChanged() method to be notified of the values when they are known, or you can use the getWidth() and getHeight() methods later, such as in the onDraw() method.

Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
Mark B
  • 183,023
  • 24
  • 297
  • 295
  • 2
    So does that mean I need to override ImageView just to catch the `onSizeChange()` event to know the real size? That seems like a bit of overkill... though at this point I'm desperate just to get the code working, so it's worth a shot. – Nik Reiman Sep 23 '10 at 17:34
  • I was thinking more of like waiting till your onCreate() has finished in your Activity. – Mark B Sep 23 '10 at 18:00
  • 1
    As mentioned, I tried putting this code in `onPostCreate()` which gets run after the view is completely created and started. – Nik Reiman Sep 24 '10 at 07:39
  • 1
    Where is the quoted text from? – Intrications Nov 02 '11 at 19:39
  • very smart answer (no..).., override all used views (create customs) and use `onSizeChanged()`, are you serious? – user924 Mar 10 '18 at 01:08
  • 1
    this quote for custom view, don't post it for such answer when we ask about view size in general – user924 Mar 10 '18 at 01:09
16

Based on @mbaird's advice, I found a workable solution by subclassing the ImageView class and overriding onLayout(). I then created an observer interface which my activity implemented and passed a reference to itself to the class, which allowed it to tell the activity when it was actually finished sizing.

I'm not 100% convinced that this is the best solution (hence my not marking this answer as correct just yet), but it does work and according to the documentation is the first time when one can find the actual size of a view.

Nik Reiman
  • 39,067
  • 29
  • 104
  • 160
  • Good to know. If I find anything that would allow you to get around having to subclass the View I'll post it here. I'm a bit surprised onPostCreate() didn't work. – Mark B Sep 24 '10 at 12:11
  • Well, I tried this and it seems to work. Just not really the best solution since this method is calles frequently not only once at start. :( – Tobias Reich Oct 25 '14 at 11:40
10

Here is the code for getting the layout via overriding a view if API < 11 (API 11 includes the View.OnLayoutChangedListener feature):

public class CustomListView extends ListView
{
    private OnLayoutChangedListener layoutChangedListener;

    public CustomListView(Context context)
    {
        super(context);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        if (layoutChangedListener != null)
        {
            layoutChangedListener.onLayout(changed, l, t, r, b);
        }
        super.onLayout(changed, l, t, r, b);
    }

    public void setLayoutChangedListener(
        OnLayoutChangedListener layoutChangedListener)
    {
        this.layoutChangedListener = layoutChangedListener;
    }
}
public interface OnLayoutChangedListener
{
    void onLayout(boolean changed, int l, int t, int r, int b);
}
cdhabecker
  • 1,693
  • 1
  • 14
  • 23
gregm
  • 12,019
  • 7
  • 56
  • 78
7

You can check this question. You can use the View's post() method.

Community
  • 1
  • 1
Macarse
  • 91,829
  • 44
  • 175
  • 230
5

This works for me in my onClickListener:

yourView.postDelayed(new Runnable() {               
    @Override
    public void run() {         
        yourView.invalidate();
        System.out.println("Height yourView: " + yourView.getHeight());
        System.out.println("Width yourView: " + yourView.getWidth());               
    }
}, 1);
Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
  • 4
    You don't need to postDelayed when it's in a click listener. You can't click anything until the views have all measured themselves and appeared on screen. Thus they will already been measured by the time you're able to click them. – afollestad Nov 25 '15 at 23:02
  • This code is wrong. Post delay does not guarantee in next tick your view is measured. – Ian Wong Aug 18 '17 at 18:40
  • I would recommend not to go for postDelayed(). It's not likely to happen but what if your view is not measured within 1 second and you call this runnable to get view width/height. It would still result 0. Edit: Oh and btw that 1 is in milliseconds, so it would fail for sure. – Alex May 17 '20 at 22:23
5

Use below code, it is give the size of view.

@Override
public void onWindowFocusChanged(boolean hasFocus) {
       super.onWindowFocusChanged(hasFocus);
       Log.e("WIDTH",""+view.getWidth());
       Log.e("HEIGHT",""+view.getHeight());
}
Asif Patel
  • 1,744
  • 1
  • 20
  • 27
4

I was also lost around getMeasuredWidth() and getMeasuredHeight() getHeight() and getWidth() for a long time.......... later i found that getting the view's width and height in onSizeChanged() is the best way to do this........ you can dynamically get your CURRENT width and CURRENT height of your view by overriding the onSizeChanged() method.

might wanna take a look at this which has an elaborate code snippet. New Blog Post: how to get width and height dimensions of a customView (extends View) in Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html

Praful Bhatnagar
  • 7,425
  • 2
  • 36
  • 44
Rakib
  • 12,376
  • 16
  • 77
  • 113
0

You can get both Position and Dimension of the view on screen

 val viewTreeObserver: ViewTreeObserver = videoView.viewTreeObserver;

if (viewTreeObserver.isAlive) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            //Remove Listener
            videoView.viewTreeObserver.removeOnGlobalLayoutListener(this);

            //View Dimentions
            viewWidth = videoView.width;
            viewHeight = videoView.height;

            //View Location
            val point = IntArray(2)
            videoView.post {
                videoView.getLocationOnScreen(point) // or getLocationInWindow(point)
                viewPositionX = point[0]
                viewPositionY = point[1]
            }

        }
    });
}
Hitesh Sahu
  • 41,955
  • 17
  • 205
  • 154
0

In Kotlin file, change accordingly

 Handler().postDelayed({

           Your Code

        }, 1)
Harshit Ruwali
  • 1,040
  • 2
  • 10
  • 22
0

If you need to know the dimensions of a View right after it is drawn you can simply call post() on that given View and send there a Runnable that executes whatever you need. It is a better solution than ViewTreeObserver and globalLayout since it gets called repeatedly not just once. This Runnsble will execute only once and you will know the views size.

Sagan
  • 112
  • 1
  • 7
-2

works perfekt for me:

 protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        CTEditor ctEdit = Element as CTEditor;
        if (ctEdit == null) return;           
        if (e.PropertyName == "Text")
        {
            double xHeight = Element.Height;
            double aHaight = Control.Height;
            double height;                
            Control.Measure(LayoutParams.MatchParent,LayoutParams.WrapContent);
            height = Control.MeasuredHeight;
            height = xHeight / aHaight * height;
            if (Element.HeightRequest != height)
                Element.HeightRequest = height;
        }
    }
ATom
  • 48
  • 4