560

I am creating all of the elements in my android project dynamically. I am trying to get the width and height of a button so that I can rotate that button around. I am just trying to learn how to work with the android language. However, it returns 0.

I did some research and I saw that it needs to be done somewhere other than in the onCreate() method. If someone can give me an example of how to do it, that would be great.

Here is my current code:

package com.animation;

import android.app.Activity;
import android.os.Bundle;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.Button;
import android.widget.LinearLayout;

public class AnimateScreen extends Activity {


//Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    LinearLayout ll = new LinearLayout(this);

    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(30, 20, 30, 0);

    Button bt = new Button(this);
    bt.setText(String.valueOf(bt.getWidth()));

    RotateAnimation ra = new RotateAnimation(0,360,bt.getWidth() / 2,bt.getHeight() / 2);
    ra.setDuration(3000L);
    ra.setRepeatMode(Animation.RESTART);
    ra.setRepeatCount(Animation.INFINITE);
    ra.setInterpolator(new LinearInterpolator());

    bt.startAnimation(ra);

    ll.addView(bt,layoutParams);

    setContentView(ll);
}

Any help is appreciated.

Patrick Favre
  • 29,166
  • 6
  • 96
  • 114
ngreenwood6
  • 7,536
  • 10
  • 29
  • 50

18 Answers18

894

The basic problem is, that you have to wait for the drawing phase for the actual measurements (especially with dynamic values like wrap_content or match_parent), but usually this phase hasn't been finished up to onResume(). So you need a workaround for waiting for this phase. There a are different possible solutions to this:

1. Listen to Draw/Layout Events: ViewTreeObserver

A ViewTreeObserver gets fired for different drawing events. Usually the OnGlobalLayoutListener is what you want for getting the measurement, so the code in the listener will be called after the layout phase, so the measurements are ready:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                view.getHeight(); //height is ready
            }
        });

Note: The listener will be immediately removed because otherwise it will fire on every layout event. If you have to support apps SDK Lvl < 16 use this to unregister the listener:

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)


2. Add a runnable to the layout queue: View.post()

Not very well known and my favourite solution. Basically just use the View's post method with your own runnable. This basically queues your code after the view's measure, layout, etc. as stated by Romain Guy:

The UI event queue will process events in order. After setContentView() is invoked, the event queue will contain a message asking for a relayout, so anything you post to the queue will happen after the layout pass

Example:

final View view=//smth;
...
view.post(new Runnable() {
            @Override
            public void run() {
                view.getHeight(); //height is ready
            }
        });

The advantage over ViewTreeObserver:

  • your code is only executed once and you don't have to disable the Observer after execution which can be a hassle
  • less verbose syntax

References:


3. Overwrite Views's onLayout Method

This is only practical in certain situation when the logic can be encapsulated in the view itself, otherwise this is a quite verbose and cumbersome syntax.

view = new View(this) {
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        view.getHeight(); //height is ready
    }
};

Also mind, that onLayout will be called many times, so be considerate what you do in the method, or disable your code after the first time


4. Check if has been through layout phase

If you have code that is executing multiple times while creating the ui you could use the following support v4 lib method:

View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
   viewYouNeedHeightFrom.getHeight();
}

Returns true if view has been through at least one layout since it was last attached to or detached from a window.

Additional: Getting staticly defined measurements

If it suffices to just get the statically defined height/width, you can just do this with:

But mind you, that this might be different to the actual width/height after drawing. The javadoc describes the difference in more detail:

The size of a view is expressed with a width and a height. A view actually possess two pairs of width and height values.

The first pair is known as measured width and measured height. These dimensions define how big a view wants to be within its parent (see Layout for more details.) The measured dimensions can be obtained by calling getMeasuredWidth() and getMeasuredHeight().

The second pair is simply known as width and height, or sometimes drawing width and drawing height. These dimensions define the actual size of the view on screen, at drawing time and after layout. These values may, but do not have to, be different from the measured width and height. The width and height can be obtained by calling getWidth() and getHeight().

Patrick Favre
  • 29,166
  • 6
  • 96
  • 114
  • onLayout was the key for me to detect the display of my view and setting the height of it based on its new width :) I've just added a flag to disable the called if not needed it anymore – Hugo Gresse Dec 04 '14 at 09:53
  • 2
    I'm using the view.post in a fragment. I need to measure the view whose parent's height is 0dp and has a weight set. Everything I try returns a height of zero (I've also tried the global layout listener). When I put the view.post code in onViewCreated with a delay of 125, it works, but not without the delay. Ideas? – Psest328 Jan 28 '15 at 21:50
  • 8
    too bad you can only upvote an answer once, really good explanation. I had a problem with a view rotation if I quit my app and restarted it from the recent apps. If I swiped it from there and did a fresh restart everything was fine. The view.post() saved my day! – Matthias Jan 16 '16 at 17:39
  • Great Solution , but the code inside onGlobalLayout ,, is called again and again and again .... why this happen ??? – S. Alawadi Feb 08 '16 at 09:58
  • Its explained in the answer "But beware that this will be called everytime something gets layouted (e.g if you set a view invisible or similar) so don't forget to remove this listener if you don't need it anymore with:" – Patrick Favre Feb 08 '16 at 10:17
  • The second one is just perfect for me. I need one height only so it's the simplest way. Thanks, dude! :) – koras Apr 08 '16 at 20:02
  • 1
    Thanks. I usually use View.post() (2nd method), but if it is called from a background thread it sometimes doesn't work. So, don't forget to write `runOnUiThread(new Runnable() {...}`. Currently I use a variation of the 1st method: `addOnLayoutChangeListener`. Don't forget to remove it then inside: removeOnLayoutChangeListener. It works from background thread as well. – CoolMind Aug 01 '16 at 07:45
  • View.post() is some voodoo magic! Thank you, worked for me when trying to get the viewed width of a horizontalscrollview. – I'm_With_Stupid Jun 11 '17 at 03:00
  • 2
    Unfortunately for me 'View.post()' is not working all the time. I have 2 projects which are very similar. In one project it's working, on the other is not working. :(. In both situation I call the method from 'onCreate()' method from an Activity. Any suggestion why? – Adrian Buciuman Sep 19 '17 at 22:45
  • 3
    I find the issue. In one project the view has `android:visibility="gone"`, and on the other project the visibility property was`invisible`. If the visibility is `gone` the width will be always 0. – Adrian Buciuman Sep 19 '17 at 22:58
  • You could also have added `onWindowFocusChanged()` as 5th option – CopsOnRoad Oct 15 '17 at 12:08
  • @Jack I don't see onWindowFocusChanged() as a good solution because it only works with activities and not really the correct callback – Patrick Favre Oct 15 '17 at 12:16
  • @for3st Thanks for letting me know :) – CopsOnRoad Oct 15 '17 at 12:18
  • Very thorough answer. Thanks for not dismissing the question with the "hardcode a value" scapegoat. – Abandoned Cart Jun 22 '19 at 05:32
263

We can use

@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }
Sana
  • 9,525
  • 14
  • 53
  • 85
  • 10
    so how can we work in Fragments ? this works only in Activity ? – Olkunmustafa Jan 04 '15 at 10:05
  • 8
    This should do the thing but its should not be the answer. A user wants to get dimensions in any point of time instead of getting drawn on the window. Also this method gets called multiple time or every time when there's a change(View hide, gone, adding new views etc..) in the window. So use it carefully :) – Dr. aNdRO Aug 20 '15 at 12:47
  • 1
    This is a hack and it seems like using the ViewTreeObserver would be better. – i_am_jorf Nov 19 '15 at 22:30
  • 1
    Don't mean to sound rude, but leaving an auto generated comment (especially in 2 lines of code) doesn't give much confidence that you actually know what you are doing. – Natix Feb 06 '16 at 23:26
  • Unluckily, it didn't work for me. Trying viewTreeObserver worked for me. – Narendra Singh Mar 16 '16 at 09:10
  • nice! I've used `setOnFocusChangeListener` to get the width and height, and worked – WiseTap Oct 12 '17 at 17:46
215

You are calling getWidth() too early. The UI has not been sized and laid out on the screen yet.

I doubt you want to be doing what you are doing, anyway -- widgets being animated do not change their clickable areas, and so the button will still respond to clicks in the original orientation regardless of how it has rotated.

That being said, you can use a dimension resource to define the button size, then reference that dimension resource from your layout file and your source code, to avoid this problem.

CommonsWare
  • 910,778
  • 176
  • 2,215
  • 2,253
  • Thanks for the reply. I actually found another solution that worked for me, but this is a nice detail to know. – ngreenwood6 Aug 30 '10 at 20:59
  • 84
    ngreenwood6, what is your other solution? – Andrew Feb 05 '11 at 03:49
  • 12
    @Andrew - if you want negreenwood6 to be notified of your follow up, you have to start your message like I did to you (I think first three letters is enough) - CommonsWare gets notified automatically, since he wrote this response, but ngreen doesn't unless you address them. – Peter Ajtai Nov 04 '11 at 17:45
  • 11
    @Override public void onWindowFocusChanged(boolean hasFocus) { // TODO Auto-generated method stub super.onWindowFocusChanged(hasFocus); //Here you can get the size! } – Sana Apr 12 '12 at 06:26
  • 5
    @ngreenwood6 So what was your solution? – starcodex Feb 27 '14 at 05:35
  • 6
    Use this listener to get size, when is your screen ready. view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {} – Sinan Dizdarević Nov 04 '15 at 15:07
  • 3
    lol, he just ran out with the confirmation of impossibility, then people here are in dead-lock. – KoreanXcodeWorker Jul 12 '17 at 10:18
115

I used this solution, which I think is better than onWindowFocusChanged(). If you open a DialogFragment, then rotate the phone, onWindowFocusChanged will be called only when the user closes the dialog):

    yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // Ensure you call it only once :
            yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

            // Here you can get the size :)
        }
    });

Edit : as removeGlobalOnLayoutListener is deprecated, you should now do :

@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {

    // Ensure you call it only once :
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
        yourView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
    else {
        yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }

    // Here you can get the size :)
}
Tim Autin
  • 5,663
  • 5
  • 40
  • 65
  • 4
    this is by far the best solution! because it works also on dynamic solutions where the width and height are not known and calculated. i.e. in a relative layout based on the positions/sizes of other elements. Well done! – George Pligoropoulos Aug 22 '13 at 22:41
  • 2
    This requires API 16 or higher (Jelly bean) – tomsv Apr 21 '14 at 11:57
  • 7
    It does NOT REQUIRE API 16! The only issue is method removeGlobalOnLayoutListener deprecated from API 16, but there is simple solution compatible with all api levels - http://stackoverflow.com/questions/16189525/ongloballayoutlistener-deprecation-and-compatibility – gingo Jun 26 '14 at 12:16
  • If you need to remove the listener so that doesn’t call itself many times and you having issues with different APIs, I recommend you to set a false boolean at the head of the class and then after capturing the values set it to true so that it doesn’t capture the values again. – Mansour Fahad Jan 15 '15 at 18:34
  • If you remove the listener after the first run you might get wrong dimensions: 02-14 10:18:53.786 15063-15063/PuzzleFragment: height: 1647 width: 996 02-14 10:18:53.985 15063-15063/PuzzleFragment: height: 1500 width: 996 – Leos Literak Feb 14 '16 at 09:23
  • What is `container` ? Is it the same as `yourView` ? According to this [other question](https://stackoverflow.com/q/16189525/) , it is. – gregn3 Sep 13 '17 at 11:43
  • This is best solution! Thanks – NotABot Nov 14 '17 at 06:17
  • This appears to not always work. The `OnPreDrawListener` proposed in another answer works. On the other hand using `OnPreDrawListener` isn't a good solution... – Ixx Jun 06 '18 at 06:19
76

As Ian states in this Android Developers thread:

Anyhow, the deal is that layout of the contents of a window happens after all the elements are constructed and added to their parent views. It has to be this way, because until you know what components a View contains, and what they contain, and so on, there's no sensible way you can lay it out.

Bottom line, if you call getWidth() etc. in a constructor, it will return zero. The procedure is to create all your view elements in the constructor, then wait for your View's onSizeChanged() method to be called -- that's when you first find out your real size, so that's when you set up the sizes of your GUI elements.

Be aware too that onSizeChanged() is sometimes called with parameters of zero -- check for this case, and return immediately (so you don't get a divide by zero when calculating your layout, etc.). Some time later it will be called with the real values.

Brad Larson
  • 168,330
  • 45
  • 388
  • 563
kerem yokuva
  • 761
  • 1
  • 5
  • 3
71

If you need to get width of some widget before it is displayed on screen, you can use getMeasuredWidth() or getMeasuredHeight().

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
sulu.dev
  • 939
  • 1
  • 10
  • 16
29

I would rather use OnPreDrawListener() instead of addOnGlobalLayoutListener(), since it is called a bit earlier than other listeners.

    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
    {
        @Override
        public boolean onPreDraw()
        {
            if (view.getViewTreeObserver().isAlive())
                view.getViewTreeObserver().removeOnPreDrawListener(this);
            
            // put your code here
            return true;
        }
    });
Ayaz Alifov
  • 6,718
  • 4
  • 49
  • 50
  • 4
    Note that `return false` means [*to **cancel** the current drawing pass*](https://developer.android.com/reference/android/view/ViewTreeObserver.OnPreDrawListener.html#onPreDraw()). To proceed, `return true` instead. – Pang Jul 24 '18 at 04:32
15

AndroidX has multiple extension functions that help you with this kind of work, inside androidx.core.view

You need to use Kotlin for this.

The one that best fits here is doOnLayout:

Performs the given action when this view is laid out. If the view has been laid out and it has not requested a layout, the action will be performed straight away otherwise, the action will be performed after the view is next laid out.

The action will only be invoked once on the next layout and then removed.

In your example:

bt.doOnLayout {
    val ra = RotateAnimation(0,360,it.width / 2,it.height / 2)
    // more code
}

Dependency: androidx.core:core-ktx:1.0.0

Community
  • 1
  • 1
Vlad
  • 768
  • 7
  • 17
  • This is the only correct answer. https://android.googlesource.com/platform/frameworks/support/+/android-room-release/core/ktx/src/main/java/androidx/core/view/View.kt#65 – hrules6872 Jan 21 '20 at 12:48
  • 1
    these extensions are really important and useful – Ahmed na Mar 14 '20 at 04:03
10

A Kotlin Extension to observe on the global layout and perform a given task when height is ready dynamically.

Usage:

view.height { Log.i("Info", "Here is your height:" + it) }

Implementation:

fun <T : View> T.height(function: (Int) -> Unit) {
    if (height == 0)
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                function(height)
            }
        })
    else function(height)
}
Renetik
  • 4,282
  • 37
  • 50
5

One liner if you are using RxJava & RxBindings. Similar approach without the boilerplate. This also solves the hack to suppress warnings as in the answer by Tim Autin.

RxView.layoutChanges(yourView).take(1)
      .subscribe(aVoid -> {
           // width and height have been calculated here
      });

This is it. No need to be unsubscribe, even if never called.

Odys
  • 8,319
  • 8
  • 63
  • 105
5

It happens because the view needs more time to be inflated. So instead of calling view.width and view.height on the main thread, you should use view.post { ... } to make sure that your view has already been inflated. In Kotlin:

view.post{width}
view.post{height}

In Java you can also call getWidth() and getHeight() methods in a Runnable and pass the Runnable to view.post() method.

view.post(new Runnable() {
        @Override
        public void run() {
            view.getWidth(); 
            view.getHeight();
        }
    });
Ehsan Nokandi
  • 1,225
  • 6
  • 13
  • This isn't correct answer as the view might not be measured and laid out when the posted code executes. It would be an edge case, but `.post` doesn't guarantee the view to be laid out. – d.aemon Mar 12 '20 at 14:53
  • 1
    This answer was great and worked for me. Thank you very much Ehsan – MMG Apr 15 '20 at 03:37
  • Be careful that ```getWidth()``` is measured in **pixels**, not **dp**. I lost too much time with this little detail – Andrei Manolache May 08 '21 at 12:11
3

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.

Shivam Yadav
  • 701
  • 5
  • 19
3

Maybe this helps someone:

Create an extension function for the View class

filename: ViewExt.kt

fun View.afterLayout(what: () -> Unit) {
    if(isLaidOut) {
        what.invoke()
    } else {
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                what.invoke()
            }
        })
    }
}

This can then be used on any view with:

view.afterLayout {
    do something with view.height
}
Pepijn
  • 1,219
  • 13
  • 15
3

Answer with post is incorrect, because the size might not be recalculated.
Another important thing is that the view and all it ancestors must be visible. For that I use a property View.isShown.

Here is my kotlin function, that can be placed somewhere in utils:

fun View.onInitialized(onInit: () -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (isShown) {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                onInit()
            }
        }
    })
}

And the usage is:

myView.onInitialized {
    Log.d(TAG, "width is: " + myView.width)
}
pavelperc
  • 101
  • 5
2

If you are using Kotlin

  leftPanel.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {

            override fun onGlobalLayout() {

                if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                    leftPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
                }
                else {
                    leftPanel.viewTreeObserver.removeGlobalOnLayoutListener(this)
                }

                // Here you can get the size :)
                leftThreshold = leftPanel.width
            }
        })
Hitesh Sahu
  • 31,496
  • 11
  • 150
  • 116
0

Gone views returns 0 as height if app in background. This my code (1oo% works)

fun View.postWithTreeObserver(postJob: (View, Int, Int) -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            val widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            measure(widthSpec, heightSpec)
            postJob(this@postWithTreeObserver, measuredWidth, measuredHeight)
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                @Suppress("DEPRECATION")
                viewTreeObserver.removeGlobalOnLayoutListener(this)
            } else {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        }
    })
}
0

We need to wait for view will be drawn. For this purpose use OnPreDrawListener. Kotlin example:

val preDrawListener = object : ViewTreeObserver.OnPreDrawListener {

                override fun onPreDraw(): Boolean {
                    view.viewTreeObserver.removeOnPreDrawListener(this)

                    // code which requires view size parameters

                    return true
                }
            }

            view.viewTreeObserver.addOnPreDrawListener(preDrawListener)
Artem Botnev
  • 1,370
  • 1
  • 9
  • 15
0

This is a little old, but was having trouble with this myself (needing to animate objects in a fragment when it is created). This solution worked for me, I believe it is self explanatory.

class YourFragment: Fragment() {
    var width = 0
    var height = 0


override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    val root = inflater.inflate(R.layout.fragment_winner_splash, container, false)
    container?.width.let {
        if (it != null) {
            width = it
        }
    }
    container?.height.let {
        if (it != null) {
            height = it
        }
    }
    
    return root
}
 
Boken
  • 3,207
  • 9
  • 25
  • 31
Matt Grier
  • 75
  • 5