How to retrieve the dimensions of a view?
Asked Answered
A

16

213

I have a view made up of TableLayout, TableRow and TextView. I want it to look like a grid. I need to get the height and width of this grid. The methods getHeight() and getWidth() always return 0. This happens when I format the grid dynamically and also when I use an XML version.

How to retrieve the dimensions for a view?


Here is my test program I used in Debug to check the results:

import android.app.Activity;
import android.os.Bundle;
import android.widget.TableLayout;
import android.widget.TextView;

public class appwig extends Activity {  
    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.maindemo);  //<- includes the grid called "board"
      int vh = 0;   
      int vw = 0;

      //Test-1 used the xml layout (which is displayed on the screen):
      TableLayout tl = (TableLayout) findViewById(R.id.board);  
      tl = (TableLayout) findViewById(R.id.board);
      vh = tl.getHeight();     //<- getHeight returned 0, Why?  
      vw = tl.getWidth();     //<- getWidth returned 0, Why?   

      //Test-2 used a simple dynamically generated view:        
      TextView tv = new TextView(this);
      tv.setHeight(20);
      tv.setWidth(20);
      vh = tv.getHeight();    //<- getHeight returned 0, Why?       
      vw = tv.getWidth();    //<- getWidth returned 0, Why?

    } //eof method
} //eof class
Adolpho answered 10/11, 2010 at 7:22 Comment(5)
instead of using getWidth/Height use getMeasuredWidth/Height after the layout is applied to the activity.Inamorata
Guys, all one has to do is call getHeight() and getWidth() after the Activity lifecycle has asked the views to measure themselves, in other words, do this kind of stuff in onResume() and that's it. You shouldn't expect a not-yet-laid-out object to know its dimensions, that's the whole trick. No need for the magic suggested below.Lilylivered
Calling getHeight()/Width() in onResume() did not yield > 0 value for me.Cassity
@ClassStacker What about Fragment?Keg
@Keg Please ask a complete new question and post a reference here in a comment.Lilylivered
C
420

I believe the OP is long gone, but in case this answer is able to help future searchers, I thought I'd post a solution that I have found. I have added this code into my onCreate() method:

EDITED: 07/05/11 to include code from comments:

final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

    @Override
    public void onGlobalLayout() {
        LayerDrawable ld = (LayerDrawable)tv.getBackground();
        ld.setLayerInset(1, 0, tv.getHeight() / 2, 0, 0);
        ViewTreeObserver obs = tv.getViewTreeObserver();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            obs.removeOnGlobalLayoutListener(this);
        } else {
            obs.removeGlobalOnLayoutListener(this);
        }
    }

});

First I get a final reference to my TextView (to access in the onGlobalLayout() method). Next, I get the ViewTreeObserver from my TextView, and add an OnGlobalLayoutListener, overriding onGLobalLayout (there does not seem to be a superclass method to invoke here...) and adding my code which requires knowing the measurements of the view into this listener. All works as expected for me, so I hope that this is able to help.

Cairo answered 10/12, 2010 at 5:50 Comment(22)
Thank you, this worked. It does get called more than once, but that can be handled by a simple comparison to previously recorded dimensions and if they changed then I can justify the CPU required to make adjustments.Saluki
@George Bailey: One thing I've recently seen someone else post (I haven't tested it myself, so YMMV) to overcome the multiple calls was (within onGlobalLayout()): tv.getViewTreeObserver().removeGlobalOnLayoutListener(this); that way the listener should be removed after the first time it occurs.Cairo
I want to help back too. You may need to do something like : mView.getViewTreeObserver().removeGlobalOnLayoutListener(globalLayoutListener); if you want to resize your view only once. Hope this helpHomophonous
thnx for the pointer to this. a little addition that wasn't immediatly obvious to me: make sure your View (in case it's not just a TextView but any View) has a background. in case it dosent, set it to a LayerDrawable like this before you make a copy and set it to final: tv.setBackgroundDrawable(getResources().getDrawable(R.drawable.layerdrawable));Delsiedelsman
The question is very old, but using addOnLayoutChangeListener works as well! :)Bystreet
Wierd. Because I am trying to add TextViews dynamically to LinearLayouts (horizontal orientation) so that they wrap (put into the next LinearLayout) if they dont fit the parent. So I need to get TextViews width and LinearLayouts` width to calculate whether they fit or not. Surprisingly I can get the TextViews width with getMeasuredWidth. But the LinearLayouts (which are predefined in xml files) return 0 from getMeasuredWidth. It is wierd because TextViews are definitely not drawn but LinearLayouts yes! So why I cant get the width?Peewit
Also, note that since API 16, you need: if(android.os.Build.VERSION.SDK_INT>=16){ view.getViewTreeObserver().removeOnGlobalLayoutListener(this); }else{ view.getViewTreeObserver().removeGlobalOnLayoutListener(this); }Chamfer
As removeGlobalOnLayoutListener is deprecated it still gives warnings in Eclipse. Is this normal? With time, deprecated code warnings would flood all over...Coulometer
@Coulometer They're just warnings -- it's normal. If you understand the warnings and want to remove them, just add @SuppressWarnings("deprecation") above the line where you use the deprecated code.Cairo
Sure... I still don't think it's the right way to do it. Anyway I settled with the simpler onWindowFocusChanged solution, bypassing this problem.Coulometer
@Coulometer keep in mind this answer was posted 2 years ago before the deprecation. :)Cairo
THANKS, it really helps me adjust my view layout parameters after adding view to it and measuring height/widthOyler
When I need to retrieve width/height of a View I used in setContentView, I retrieve the root of my layout and post a runnable on it. This way, this runnable is put at the end of the queue of the looper of the UI Thread. When the runnable I posted is processed the layout has been measured, so getWidth/getHeight does no return 0. Is my approach wrong?Underlaid
@blackbelt That's probably a stable approach as well, this just seems a bit more declarative.Cairo
@kcoppock thanks for the answer. What does "declarative" mean?Underlaid
@blackbelt I just mean that it's very descriptive of when it should happen. Posting a runnable should always occur after the layout is finished, but specifically stating that it should happen on layout just seems like it'd be easier to understand the reasoning when you go back and see the code later. All in all, I think both options are fine. :)Cairo
@Chamfer I've updated the answer to include that code to keep the answer up-to-date.Gurley
Your answer help me but as soon as I use marquee text with GlobalLayout marquee does not scroll. can you help me in this ques- #18398128Izak
I want to retrive the size of a view of a progressbar, but ld is null: LayerDrawable ld = (LayerDrawable)tv.getBackground(); Can I in the same onCreate get the size of this progressbar and use this value to change the margin of another view?Otes
for fragments use it in onResume callbackKatha
This solution does works for most of the cases but in my case i have a fragment in which a RelativeLayout is included using <include>. Inside onGlobalLayout() the RelativeLayout height is still 0!Gradygrae
@zest getHeight() still returns 0?Gradygrae
B
84

I'll just add an alternative solution, override your activity's onWindowFocusChanged method and you will be able to get the values of getHeight(), getWidth() from there.

@Override
public void onWindowFocusChanged (boolean hasFocus) {
        // the height will be set at this point
        int height = myEverySoTallView.getMeasuredHeight(); 
}
Brentwood answered 3/7, 2011 at 2:22 Comment(6)
To quote the documentation, "This is the best indicator of whether this activity is visible to the user."Retortion
This doesn't work in case of Fragments for that you have to use ViewTreeObserver.Dishonest
OP specifically asked how to get it for a view thoughBrentwood
scrollview total height and max Scrollview.getScrolly() will be same ? i.e. In my case I got Height with above method is 702 and I got maximum value from getScrolly() is 523,Superhighway
This method also doesn't work when your View is being included from another xml file.Kandis
I have an interesting "doesn't work for me" case: in my code I'm measuring available width/height and massaging some font sizes based on what I get back. This in turn causes a RE-LAYOUT. I just checked this call and it's after the first layout is complete, just after the first round of globallayoutlistener calls. I end up using the SECOND result from the globallayoutlistener as accurate for some field, because I'm changing some others after the first! So I put a counter in there and don't disable after the first pass through onGlobalLayout for this second group of fields.Clothespress
M
20

You are trying to get width and height of an elements, that weren't drawn yet.

If you use debug and stop at some point, you'll see, that your device screen is still empty, that's because your elements weren't drawn yet, so you can't get width and height of something, that doesn't yet exist.

And, I might be wrong, but setWidth() is not always respected, Layout lays out it's children and decides how to measure them (calling child.measure()), so If you set setWidth(), you are not guaranteed to get this width after element will be drawn.

What you need, is to use getMeasuredWidth() (the most recent measure of your View) somewhere after the view was actually drawn.

Look into Activity lifecycle for finding the best moment.

http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle

I believe a good practice is to use OnGlobalLayoutListener like this:

yourView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (!mMeasured) {
                // Here your view is already layed out and measured for the first time
                mMeasured = true; // Some optional flag to mark, that we already got the sizes
            }
        }
    });

You can place this code directly in onCreate(), and it will be invoked when views will be laid out.

Misname answered 11/11, 2010 at 8:27 Comment(2)
Unfortunately it is not working in OnResume as well. Final dimensions are set somewhere after OnResume call, at least in emulator.Fulvi
That's a horrible practise !!! This listener will be called over and over and will kill your performace. Instead of using flags, unregister the listener once done.Footwear
F
16

Use the View's post method like this

post(new Runnable() {   
    @Override
    public void run() {
        Log.d(TAG, "width " + MyView.this.getMeasuredWidth());
        }
    });
Frigg answered 6/12, 2011 at 15:20 Comment(1)
That works, but really, no discussion of why? Btw, this works because the runnable doesn't get run until the layout has occurred.Hessler
S
7

I tried to use onGlobalLayout() to do some custom formatting of a TextView, but as @George Bailey noticed, onGlobalLayout() is indeed called twice: once on the initial layout path, and second time after modifying the text.

View.onSizeChanged() works better for me because if I modify the text there, the method is called only once (during the layout pass). This required sub-classing of TextView, but on API Level 11+ View. addOnLayoutChangeListener() can be used to avoid sub-classing.

One more thing, in order to get correct width of the view in View.onSizeChanged(), the layout_width should be set to match_parent, not wrap_content.

Sudderth answered 10/7, 2012 at 21:48 Comment(0)
M
2

Are you trying to get sizes in a constructor, or any other method that is run BEFORE you get the actual picture?

You won't be getting any dimensions before all components are actually measured (since your xml doesn't know about your display size, parents positions and whatever)

Try getting values after onSizeChanged() (though it can be called with zero), or just simply waiting when you'll get an actual image.

Misname answered 10/11, 2010 at 7:33 Comment(1)
I updated my original question and code to be clearer. Thank you.Adolpho
A
2

I guess this is what you need to look at: use onSizeChanged() of your view. Here is an EXTENDED code snippet on how to use onSizeChanged() to get your layout's or view's height and width dynamically http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html

Aseptic answered 27/2, 2011 at 20:25 Comment(0)
L
2

As F.X. mentioned, you can use an OnLayoutChangeListener to the view that you want to track itself

view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        // Make changes
    }
});

You can remove the listener in the callback if you only want the initial layout.

Lair answered 2/1, 2014 at 9:40 Comment(0)
P
1

ViewTreeObserver and onWindowFocusChanged() are not so necessary at all.

If you inflate the TextView as layout and/or put some content in it and set LayoutParams then you can use getMeasuredHeight() and getMeasuredWidth().

BUT you have to be careful with LinearLayouts (maybe also other ViewGroups). The issue there is, that you can get the width and height after onWindowFocusChanged() but if you try to add some views in it, then you can't get that information until everything have been drawn. I was trying to add multiple TextViews to LinearLayouts to mimic a FlowLayout (wrapping style) and so couldn't use Listeners. Once the process is started, it should continue synchronously. So in such case, you might want to keep the width in a variable to use it later, as during adding views to layout, you might need it.

Peewit answered 20/8, 2012 at 23:4 Comment(0)
A
1

Even though the proposed solution works, it might not be the best solution for every case because based on the documentation for ViewTreeObserver.OnGlobalLayoutListener

Interface definition for a callback to be invoked when the global layout state or the visibility of views within the view tree changes.

which means it gets called many times and not always the view is measured (it has its height and width determined)

An alternative is to use ViewTreeObserver.OnPreDrawListener which gets called only when the view is ready to be drawn and has all of its measurements.

final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnPreDrawListener(new OnPreDrawListener() {

    @Override
    public void onPreDraw() {
        tv.getViewTreeObserver().removeOnPreDrawListener(this);
        // Your view will have valid height and width at this point
        tv.getHeight();
        tv.getWidth();
    }

});
Anselm answered 12/7, 2018 at 17:54 Comment(0)
L
1

Height and width are zero because view has not been created by the time you are requesting it's height and width . One simplest solution is

view.post(new Runnable() {
    @Override
    public void run() {
        view.getHeight(); //height is ready
        view.getWidth(); //width is ready
    }
});

This method is good as compared to other methods as it is short and crisp.

Longshoreman answered 15/3, 2019 at 10:30 Comment(1)
This answer is surely working but on devices with low hardware specification, a performance lag can be observed using this implementation.Quasijudicial
S
0

You should rather look at View lifecycle: http://developer.android.com/reference/android/view/View.html Generally you should not know width and height for sure until your activity comes to onResume state.

Sherris answered 13/11, 2010 at 22:52 Comment(0)
M
0

You can use a broadcast that is called in OnResume ()

For example:

int vh = 0;   
int vw = 0;

public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.maindemo);  //<- includes the grid called "board"

      registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                TableLayout tl = (TableLayout) findViewById(R.id.board);  
                tl = (TableLayout) findViewById(R.id.board);
                vh = tl.getHeight();       
                vw = tl.getWidth();               
            }
      }, new IntentFilter("Test"));
}

protected void onResume() {
        super.onResume();

        Intent it = new Intent("Test");
        sendBroadcast(it);

}

You can not get the height of a view in OnCreate (), onStart (), or even in onResume () for the reason that kcoppock responded

Monstrance answered 25/9, 2017 at 1:56 Comment(0)
M
-1

Simple Response: This worked for me with no Problem. It seems the key is to ensure that the View has focus before you getHeight etc. Do this by using the hasFocus() method, then using getHeight() method in that order. Just 3 lines of code required.

ImageButton myImageButton1 =(ImageButton)findViewById(R.id.imageButton1); myImageButton1.hasFocus();

int myButtonHeight = myImageButton1.getHeight();

Log.d("Button Height: ", ""+myButtonHeight );//Not required

Hope it helps.

Microsecond answered 21/1, 2014 at 2:19 Comment(0)
I
-3

Use getMeasuredWidth() and getMeasuredHeight() for your view.

Developer guide: View

Izanami answered 10/11, 2010 at 10:9 Comment(1)
As stated before these methods only return a valid result efter the size has been meassured. Within an Activity this holds true when the method onWindowFocusChanged os called.Denticle
W
-8

CORRECTION: I found out that the above solution is terrible. Especially when your phone is slow. And here, I found another solution: calculate out the px value of the element, including the margins and paddings: dp to px: https://mcmap.net/q/36300/-converting-pixels-to-dp

or dimens.xml to px: https://mcmap.net/q/80807/-load-dimension-value-from-res-values-dimension-xml-from-source-code

sp to px: https://mcmap.net/q/111808/-convert-pixels-to-sp (reverse the solution)

or dimens to px: https://mcmap.net/q/80807/-load-dimension-value-from-res-values-dimension-xml-from-source-code

and that's it.

Witenagemot answered 21/8, 2013 at 3:30 Comment(1)
It's usually a bad idea to hope that everything will be set correctly after some arbitrarily chosen number of milliseconds. Device might be slow, other processes/threads might be running, might not work in debugger, might not work in next version of OS, ...Leafage

© 2022 - 2024 — McMap. All rights reserved.