Android: Programmatic 'allow reorientation'

2020-07-22 18:18发布

问题:

I have a CheckBoxPreference with key "allow_reorientation". If enabled then my main activity should reorient upon device rotation, and if disabled it should remain in its current orientation. I have set 'android:configChanges="orientation"' in my manifest.xml to allow custom handling of orientation changes.

From my main activity class:

@Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        if (preferences.getBoolean("allow_reorientation", true)) {
            switch(newConfig.orientation) {
                case Configuration.ORIENTATION_PORTRAIT:
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                    break;
                case Configuration.ORIENTATION_LANDSCAPE:
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                    break;
                default:
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
                    break;
            }
        }
    }

If I start my application in portrait, then rotate the device to landscape, the above code is called and behaves as desired. However, the method is not called at all when rotating back to portrait, and of course the view remains in landscape.

My first guess was that since I don't store the 'newConfig.orientation' anywhere, the second reorientation back to portrait was comparing the latest orientation value against a pre-existing value that still indicated portrait orientation, and thus no configuration change was detected. However, if this were true then a breakpoint I've set in the above method would be activated when I attempt to orient to landscape a second time, yet the method is never called in this circumstance.

Could someone please elucidate what's going on, and offer a remedy?

Many thanks.

SOLUTION - Thanks go to Matthew Willis below

In YourApp which extends Application:

public void updateOrientationConfiguration(Activity activity) {
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
    if (preferences.getBoolean("allow_reorientation", true)) {
        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
    } else {
        if (activity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        }
        if (activity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
            activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
    }
}

In YourActivity which extends Activity:

    public void onResume() {
        super.onResume();
        YourApp app = (YourApp)this.getApplication();
        app.updateOrientationConfiguration(this);
    }

回答1:

An alternative method is to use ActivityInfo.SCREEN_ORIENTATION_SENSOR and ActivityInfo.SCREEN_ORIENTATION_NOSENSOR.

You would basically set one or the other based on your preference and then you don't have to implement onConfigurationChanged (or set configChanges in your manifest).

An example activity:

public class Main extends Activity {
    private static boolean locked = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        doLock();
    }

    public void doLock() {
        if (locked) {
            if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
            }
            if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            }
        } else {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
        }
    }

    public void toggle(View v) {
        locked = !locked;
        doLock();
    }
}

In testing I noticed that SCREEN_ORIENTATION_NOSENSOR also causes a rotate to the default orientation, so it is necessary to use _LANDSCAPE and _PORTRAIT explicitly.



回答2:

I'm not by any means certain, but it could be weirdness with setRequestedOrientation - I'd expect this to fire onConfigurationChanged - it might be worth overriding setRequestedOrientation to test when it gets called.

Are you doing functional testing after changing orientation, or just looking at the screen? It may be that the super.onConfigurationChanged() is where the layout changes, and the code is getting stuck in a loop when you call setRequestedOrientation

Hope this helps,

Phil Lello