22

One of our views has a ScrollView as its root layout. When the device is rotated and onConfigurationChanged() is called, we'd like to be able to get the ScrollView's new width/height. Our code looks like this:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    Log.d(TAG, "Width: '" + findViewById(R.id.scrollview).getWidth() + "'");
    Log.d(TAG, "Height: '" + findViewById(R.id.scrollview).getHeight() + "'");

    super.onConfigurationChanged(newConfig);

    Log.d(TAG, "Width: '" + findViewById(R.id.scrollview).getWidth() + "'");
    Log.d(TAG, "Height: '" + findViewById(R.id.scrollview).getHeight() + "'");
}

The relevant section of our AndroidManifest.xml looks like this:

<activity android:name=".SomeActivity"
    android:configChanges="keyboardHidden|orientation">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
    </intent-filter>
</activity>

And finally, the relevant portion of our layout looks like this:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollview"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent"
    >
    <LinearLayout android:id="@+id/container"
        android:orientation="vertical"
        android:layout_height="fill_parent"
        android:minHeight="200dip"
        android:layout_width="fill_parent"
        >

On our Droid, we expected to see the ScrollView's width go to 854 when switched into landscape, and to 480 when switched back to portrait (and the height do the equivalent switch, minus the menu bar). However, we're seeing the opposite. Here's our LogCat:

// Switching to landscape:
03-26 11:26:16.490: DEBUG/ourtag(17245): Width: '480'  // Before super
03-26 11:26:16.490: DEBUG/ourtag(17245): Height: '778' // Before super
03-26 11:26:16.529: DEBUG/ourtag(17245): Width: '480'  // After super
03-26 11:26:16.536: DEBUG/ourtag(17245): Height: '778' // After super

// Switching to portrait:
03-26 11:26:28.724: DEBUG/ourtag(17245): Width: '854'  // Before super
03-26 11:26:28.740: DEBUG/ourtag(17245): Height: '404' // Before super
03-26 11:26:28.740: DEBUG/ourtag(17245): Width: '854'  // After super
03-26 11:26:28.740: DEBUG/ourtag(17245): Height: '404' // After super

Clearly, we're getting the portrait dimensions when we switch to landscape, and the landscape dimensions when we switch to portrait. Is there something we're doing wrong? We could get hacky and solve this, but I feel like there's a simple solution that we're missing.

jakeboxer
  • 3,300
  • 4
  • 26
  • 27

3 Answers3

38

For those looking for a more detailed solution description: You can use the ViewTreeObserver of your view and register an OnGlobalLayoutListener.

@Override
public void onConfigurationChanged(Configuration newConfiguration) {
    super.onConfigurationChanged(newConfiguration);
    final View view = findViewById(R.id.scrollview);

    ViewTreeObserver observer = view.getViewTreeObserver();
    observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            Log.v(TAG,
                    String.format("new width=%d; new height=%d", view.getWidth(),
                            view.getHeight()));
            view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
    });
}
Andreas Klöber
  • 5,855
  • 2
  • 27
  • 20
  • 6
    To avoid an infinite loop of calls to `onGlobalLayout`, I had to remove the listener after making my calculations based on the new sizes of the views: `view.getViewTreeObserver().removeGlobalOnLayoutListener(this);` – Sébastien Aug 25 '12 at 14:18
  • 1
    thank you i had forgotten about viewTreeObservers ...also for others clarification: removeGlobalOnLayoutListener is deprecated. use "removeOnGlobalLayoutListener" instead. – j2emanue Sep 29 '13 at 17:00
  • 6
    This gives inconsistent results. Sometimes it gets it right, sometimes not. IMO listening to a resize event in a custom `View` is most likely the right way to go. – Christopher Perry Dec 30 '13 at 18:21
  • nice way to make the deal, it is also smooth during roration – Alessandro Cabutto Jun 16 '14 at 13:10
  • agree with @ChristopherPerry, tried this but sometime it just doesn't have any effect and gives the same values. Better use custom view and override onSizeChanged event and that will work always. – KunalK May 10 '19 at 08:57
16

When onGlobalLayout called it's not sure that the view has been resized accordingly to the new layout orientation, so for me only the below solution worked properly:

@Override
public void onConfigurationChanged(Configuration newConfig) {
final int oldHeight = mView.getHeight();
final int oldWidth = mView.getWidth();

mView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (mView.getHeight() != oldHeight && mView.getWidth() != oldWidth) {
                mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                //mView now has the correct dimensions, continue with your stuff
            }
        }
    });
    super.onConfigurationChanged(newConfig);}
Antonis Lat
  • 545
  • 4
  • 9
  • Thanks, I also had to check that the size changed before removing the listener. Now onGlobalLayout is called twice and I get the right size only the second time. I don't know why though :/ – Tim Autin Jun 08 '15 at 20:29
  • Should be the accepted answer. Though checking both height and width is not necessary, one is enough. – 0101100101 Aug 05 '15 at 07:15
  • definitely needed in the case the user has tweaked with animation speeds in the phone settings – rupps Nov 03 '16 at 01:37
  • What if the onConfigurationChanged is called in other cases than rotation? – cylon Apr 20 '22 at 12:29
7

getWidth() returns the width as the View is laid out, meaning you need to wait until it is drawn to the screen. onConfigurationChanged is called before redrawing the view for the new configuration and so I don't think you'll be able to get your new width until later.

jqpubliq
  • 11,874
  • 2
  • 34
  • 26
  • That's what we were afraid of. The first solution we can think of is to spawn a thread, wait 100ms or something, and then grab the width/height. Is there anything less hacky we can do than that? – jakeboxer Mar 26 '10 at 18:07
  • 13
    Spawning a thread? Oh no do not do this! You can simply post a Runnable in a Handler (or on the UI thread using View.post() for instance.) You can also register a listener with the ViewTreeObserver, wait for a resize change event in a custom View, etc. – Romain Guy Mar 26 '10 at 18:51
  • Listening to the ViewTreeObserver sounds like the best solution to me as I do find the waiting method a bit hacky. – jqpubliq Mar 26 '10 at 19:06
  • Ah, excellent. We'd never even heard of the ViewTreeObserver before. Romain Guy, if you post that as an answer, I'll vote it up and accept it. Otherwise, I'll just accept this one. – jakeboxer Mar 26 '10 at 19:45
  • 1
    I really don't get what the point of having a Configuration object called newConfig passed into the method if you can't use it to get the new height and width. The Configuration class even has screenWidthDp and screenHeightDp class members. – Marty Miller Mar 08 '12 at 03:18
  • runonuithread gets called immediately before the layouting is done at least, same seems true for post, so yeah you would need to spawn a new thread if you used the waiting method. use the viewtreeobserver since that's less hacky. – Lassi Kinnunen Apr 26 '13 at 10:47