I’ve noticed that onLayout and onSizeChanged get called twice in immediate succession after an orientation change, either from landscape->portrait or from portrait->landscape, when handling the configuration change from an Activity. Furthermore, the first onLayout/onSizeChanged contain the old dimensions (before the rotate), while the 2nd onLayout/onSizeChanged contain the new (correct) dimensions.
Does anyone know why, and/or how to work around this? It seems like perhaps the screen size change happens quite some time after the configuration change – i.e., the dimensions are not correct immediately after the configuration change when onConfigurationChanged is called?
Here’s the debug output of the code below, showing both onLayout/onSizeChanged calls after a rotation from Portrait to Landscape (note that the device is 540×960, so the landscape width should be 960 while the portrait width is 540):
03-13 17:36:21.140: DEBUG/RotateTest(27765): onConfigurationChanged: LANDSCAPE
03-13 17:36:21.169: DEBUG/RotateTest(27765): onSizeChanged:540,884,0,0
03-13 17:36:21.189: DEBUG/RotateTest(27765): onLayout:true-0,0,540,884
03-13 17:36:21.239: DEBUG/RotateTest(27765): onSizeChanged:960,464,540,884
03-13 17:36:21.259: DEBUG/RotateTest(27765): onLayout:true-0,0,960,464
Note also that the first onSizeChanged oldwidth and oldheight are 0, indicating that we were just added to the view hierarchy – yet with the wrong dimensions for landscape!
And here is the code that illustrates this behavior:
MyActivity.java
package com.example;
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.widget.FrameLayout;
public class MyActivity extends Activity
{
private static String TAG = "RotateTest";
@Override
public void onConfigurationChanged(Configuration newConfig) {
Log.d(TAG, "onConfigurationChanged: " + (newConfig.orientation == 1 ? "PORTRAIT" : "LANDSCAPE"));
super.onConfigurationChanged(newConfig);
_setView();
}
@Override
public void onCreate(Bundle savedInstanceState)
{
Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
_setView();
}
private void _setView() {
MyHorizontalScrollView scrollView = new MyHorizontalScrollView(this, null);
setContentView(scrollView);
}
}
MyHorizontalScrollView.java
package com.example;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.HorizontalScrollView;
public class MyHorizontalScrollView extends HorizontalScrollView {
private static String TAG = "RotateTest";
public MyHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
Log.d(TAG, "onLayout:" + String.format("%s-%d,%d,%d,%d", changed, l, t, r, b));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.d(TAG, "onSizeChanged:" + String.format("%d,%d,%d,%d", w, h, oldw, oldh));
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example"
android:versionCode="1"
android:versionName="1.0"
>
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="9"/>
<application android:label="@string/app_name"
>
<activity android:name="MyActivity"
android:label="@string/app_name"
android:configChanges="orientation"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
I’ve been wondering this myself for a very long time.
The way I answered the question–because I do believe the answer is, it depends–is by adding a
try/catchor logging statement in therequestLayoutmethod. This allows you to see when the requests for remeasuring and re-laying out are made, and in the case of thetry/catch,by whom.The way laying out in Android works is that you mark a view as having a dirty layout with
requestLayout. An Android looper which always runs on the UI thread on some interval will remeasure and re-layout views in the tree that have been marked as dirty at some indeterminate point in the future.I venture to guess that
onConfigurationChanged, you are getting severalrequestLayoutcalls and the looper is callingonMeasuresomewhere in the middle of them.This is what the logging looked like for me:
The Android documentation has more information on measuring and laying out, but is sadly short on the specifics I described above.