22

I've seen a few other questions similar to this but no answers.

Rotating from portrait to landscape (either direction) and back again, we get the helpful call to onConfigurationChanged().

However, when rotating from landscape to landscape (through 180 degrees) onConfigurationChanged() is not called.

I've seen mention of using OrientationEventListener but this seems flakey to me because you can rotate quickly around without triggering a display orientation change.

I've tried adding a layout change listener, but with no success.

So the question is, how to reliably detect such a change in landscape orientation?

Mark
  • 7,446
  • 5
  • 55
  • 75

4 Answers4

15

OrientationEventlistener won't work when the device isn't rotating/moving.

I find display listener is a better way to detect the change.

     DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
        @Override
        public void onDisplayAdded(int displayId) {
           android.util.Log.i(TAG, "Display #" + displayId + " added.");
        }

        @Override
        public void onDisplayChanged(int displayId) {
           android.util.Log.i(TAG, "Display #" + displayId + " changed.");
        }

        @Override
        public void onDisplayRemoved(int displayId) {
           android.util.Log.i(TAG, "Display #" + displayId + " removed.");
        }
     };
     DisplayManager displayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
     displayManager.registerDisplayListener(mDisplayListener, UIThreadHandler);
superuser
  • 768
  • 7
  • 9
  • 2
    Note: this requires API Level 17+ – lapis May 14 '14 at 19:08
  • why do you need OrientationEventlistener to work when the device isn't rotating/moving ? – shaktiman_droid May 14 '14 at 19:32
  • My app need to provide fresh orientation status, but sometimes orientation could be changed by calling API. Finally I used a thread to pull its status.. No better solution indeed since this only works with API 17+. – superuser May 22 '14 at 14:57
  • 4
    THIS should be the accepted answer. `onDisplayChanged` triggers only exactly when `getRotation()` changes. `OrientationEventListener` on the other hand triggers constantly in real time and even before `getRotation()` changes, leading to completely wrong screen rotation values when doing a 180° turn. – 0101100101 Feb 21 '18 at 23:57
  • In my case DisplayListener doesnt work on Android TV Api 17. Any suggestions? – bene25 Sep 14 '18 at 14:19
6

May be you should add some logical code in your OrientationEventListener like this:

mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

OrientationEventListener orientationEventListener = new OrientationEventListener(this,
        SensorManager.SENSOR_DELAY_NORMAL) {
    @Override
    public void onOrientationChanged(int orientation) {

        Display display = mWindowManager.getDefaultDisplay();
        int rotation = display.getRotation();
        if ((rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) && rotation != mLastRotation) {
            Log.i(TAG, "changed >>> " + rotation);

            // do something

            mLastRotation = rotation;
        }
    }
};

if (orientationEventListener.canDetectOrientation()) {
    orientationEventListener.enable();
}
c0ming
  • 3,407
  • 1
  • 21
  • 26
  • Why only 90 and 270 degrees? Phones usually cannot be upside down, but tablets do. The same problem is when you rotate 180deg. from portrait. – Oliv Mar 10 '16 at 13:37
  • 2
    Beware of this answer! `OrientationEventListener` is not the correct tool for 180° orientation changes, because it triggers constantly in real time and even before `getRotation()` changes! `DisplayListener` does a much better job as it triggers only exactly when `getRotation()` changes. – 0101100101 Feb 21 '18 at 23:52
  • @0101100101 VERY TRUE, sometimes rotation doesn't even happen when OrientationEventListener says that it happened – user924 May 02 '18 at 11:56
  • guys DON'T USE THIS, you can have delayed orientation changes or sometimes it when you rotate it won't change orientation, so this method won't tell the correct current orientation of your app – user924 May 02 '18 at 11:57
3

I use this code to have it work for my case.

  OrientationEventListener mOrientationEventListener = new OrientationEventListener(mActivity)
  {
     @Override
     public void onOrientationChanged(int orientation)
     {
        if (orientation == ORIENTATION_UNKNOWN) return;

        int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
        switch (rotation) {
          case Surface.ROTATION_0:
             android.util.Log.i(TAG, "changed ROTATION_0 - " + orientation);
             break;
          case Surface.ROTATION_90:
             android.util.Log.i(TAG, "changed ROTATION_90 - " + orientation);
             break;
          case Surface.ROTATION_180:
             android.util.Log.i(TAG, "changed ROTATION_180 - " + orientation);
             break;
          case Surface.ROTATION_270:
             android.util.Log.i(TAG, "changed ROTATION_270 - " + orientation);
             break;
        }
        if ((rotation != mLastRotation) && (rotation & 0x1) == (mLastRotation & 0x1))
        {
           android.util.Log.i(TAG, "unhandled orientation changed >>> " + rotation);
        }
        mLastRotation = rotation;
     }
  };

  if (mOrientationEventListener.canDetectOrientation()){
     mOrientationEventListener.enable();
  }
superuser
  • 768
  • 7
  • 9
  • 2
    Beware of this answer! `OrientationEventListener` is not the correct tool for 180° orientation changes, because it triggers constantly in real time and even before `getRotation()` changes! `DisplayListener` does a much better job as it triggers only exactly when `getRotation()` changes. – 0101100101 Feb 21 '18 at 23:53
  • guys DON'T USE THIS, you can have delayed orientation changes or sometimes it when you rotate it won't change orientation, so this method won't tell the correct current orientation of your app – user924 May 02 '18 at 11:57
  • Not to mention in Android 9 they added the new button to allow the user to change the orientation of the app (or not), but this will still trigger the code to rotate – 1800 INFORMATION Feb 06 '20 at 22:28
1

You'd still want to use the display listener class: Google recommends it for handling 180-degree device rotations from an unlocked orientation. You cannot simply treat all callbacks to onDisplayChanged as 180 degree rotations. A more complete implementation from Google shows how to gather the Display rotation from the Display manager during the onDisplayChanged callback that superuser mentioned.

/** DisplayManager to listen to display changes */
private val displayManager: DisplayManager by lazy {
    applicationContext.getSystemService(DISPLAY_SERVICE) as DisplayManager
}

/** Keeps track of display rotations */
private var displayRotation = 0

...

override fun onAttachedToWindow() {
    super.onAttachedToWindow()
    displayManager.registerDisplayListener(displayListener, mainLooperHandler)
}

override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    displayManager.unregisterDisplayListener(displayListener)
}

private val displayListener = object : DisplayManager.DisplayListener {
    override fun onDisplayAdded(displayId: Int) {}
    override fun onDisplayRemoved(displayId: Int) {}
    override fun onDisplayChanged(displayId: Int) {
        val difference = displayManager.getDisplay(displayId).rotation - displayRotation
        displayRotation = displayManager.getDisplay(displayId).rotation

        if (difference == 2 || difference == -2) {
            createCaptureSession()
        }
    }
}
S. Messick
  • 11
  • 2