Listen to device orientation changes

Summary:
Similar to iOS, send device orientation changes events. This does not have the
`getCurrentOrientation` method, because it's not used. If necessary, we'll
add it separately.
This also adds a simple example for testing.

We listen to orientation changes in `onGlobalLayout`, and check if the rotation of the device has changed. If it has, we emit the event.
But:
- `onGlobalLayout` (and `onConfigurationChanged` - which is the method usually used for checking for device orientation changes) is *not* called when the device goes from landscape
  to reverse landscape (same with portrait), as that is not a relayout / configuration change. We could detect if this happens with the help of an `OrientationEventListener`. However, this listener notifies you if the degree
  of the phone changes by a single degree, which means that you need to know by how many degrees the phone needs to change in order for the orientation to change. I haven't looked into how accurate this could be, but I suspect that in practice it would cause a lot of bugs. A simple `abgs` and google search reveals that everybody uses a different margin for detecting a rotation change (from 30 to 45 degrees), so I suspect that this won't work
  as expected in practice. Therefore, we're not using this here, and we're sticking to what android provides via `onConfigurationChanged`. If we find that we have issues because users need to know when the user goes
  from landscape to reverse landscape, then we'll have to revisit this.

Reviewed By: foghina

Differential Revision: D3797521

fbshipit-source-id: 62508efd342a9a4b41b42b6138c73553cfdefebc
This commit is contained in:
Andrei Coman
2016-09-06 03:54:27 -07:00
committed by Facebook Github Bot 4
parent 5d240a8ed3
commit f07ca31303
5 changed files with 164 additions and 25 deletions

View File

@@ -11,14 +11,17 @@ package com.facebook.react;
import javax.annotation.Nullable;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
@@ -30,10 +33,10 @@ import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.JSTouchDispatcher;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.RootView;
import com.facebook.react.uimanager.SizeMonitoringFrameLayout;
import com.facebook.react.uimanager.JSTouchDispatcher;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.events.EventDispatcher;
@@ -56,7 +59,7 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
private @Nullable ReactInstanceManager mReactInstanceManager;
private @Nullable String mJSModuleName;
private @Nullable Bundle mLaunchOptions;
private @Nullable KeyboardListener mKeyboardListener;
private @Nullable CustomGlobalLayoutListener mCustomGlobalLayoutListener;
private @Nullable OnGenericMotionListener mOnGenericMotionListener;
private int mRootViewTag;
private boolean mWasMeasured = false;
@@ -171,7 +174,7 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mIsAttachedToInstance) {
getViewTreeObserver().addOnGlobalLayoutListener(getKeyboardListener());
getViewTreeObserver().addOnGlobalLayoutListener(getCustomGlobalLayoutListener());
}
}
@@ -179,7 +182,7 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mIsAttachedToInstance) {
getViewTreeObserver().removeOnGlobalLayoutListener(getKeyboardListener());
getViewTreeObserver().removeOnGlobalLayoutListener(getCustomGlobalLayoutListener());
}
}
@@ -255,11 +258,11 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
mWasMeasured = true;
}
private KeyboardListener getKeyboardListener() {
if (mKeyboardListener == null) {
mKeyboardListener = new KeyboardListener();
private CustomGlobalLayoutListener getCustomGlobalLayoutListener() {
if (mCustomGlobalLayoutListener == null) {
mCustomGlobalLayoutListener = new CustomGlobalLayoutListener();
}
return mKeyboardListener;
return mCustomGlobalLayoutListener;
}
private void attachToReactInstanceManager() {
@@ -269,7 +272,7 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
mIsAttachedToInstance = true;
Assertions.assertNotNull(mReactInstanceManager).attachMeasuredRootView(this);
getViewTreeObserver().addOnGlobalLayoutListener(getKeyboardListener());
getViewTreeObserver().addOnGlobalLayoutListener(getCustomGlobalLayoutListener());
}
@Override
@@ -291,13 +294,14 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
mRootViewTag = rootViewTag;
}
private class KeyboardListener implements ViewTreeObserver.OnGlobalLayoutListener {
private class CustomGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
private final Rect mVisibleViewArea;
private final int mMinKeyboardHeightDetected;
private int mKeyboardHeight = 0;
private int mDeviceRotation = 0;
/* package */ KeyboardListener() {
/* package */ CustomGlobalLayoutListener() {
mVisibleViewArea = new Rect();
mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
}
@@ -305,16 +309,17 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
@Override
public void onGlobalLayout() {
if (mReactInstanceManager == null || !mIsAttachedToInstance ||
mReactInstanceManager.getCurrentReactContext() == null) {
FLog.w(
ReactConstants.TAG,
"Unable to dispatch keyboard events in JS as the react instance has not been attached");
mReactInstanceManager.getCurrentReactContext() == null) {
return;
}
checkForKeyboardEvents();
checkForDeviceOrientationChanges();
}
private void checkForKeyboardEvents() {
getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);
final int heightDiff =
DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels - mVisibleViewArea.bottom;
DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels - mVisibleViewArea.bottom;
if (mKeyboardHeight != heightDiff && heightDiff > mMinKeyboardHeightDetected) {
// keyboard is now showing, or the keyboard height has changed
mKeyboardHeight = heightDiff;
@@ -333,6 +338,52 @@ public class ReactRootView extends SizeMonitoringFrameLayout implements RootView
}
}
private void checkForDeviceOrientationChanges() {
final int rotation =
((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay().getRotation();
if (mDeviceRotation == rotation) {
return;
}
mDeviceRotation = rotation;
emitOrientationChanged(rotation);
}
private void emitOrientationChanged(final int newRotation) {
String name;
double rotationDegrees;
boolean isLandscape = false;
switch (newRotation) {
case Surface.ROTATION_0:
name = "portrait-primary";
rotationDegrees = 0.0;
break;
case Surface.ROTATION_90:
name = "landscape-primary";
rotationDegrees = -90.0;
isLandscape = true;
break;
case Surface.ROTATION_180:
name = "portrait-secondary";
rotationDegrees = 180.0;
break;
case Surface.ROTATION_270:
name = "landscape-secondary";
rotationDegrees = 90.0;
isLandscape = true;
break;
default:
return;
}
WritableMap map = Arguments.createMap();
map.putString("name", name);
map.putDouble("rotationDegrees", rotationDegrees);
map.putBoolean("isLandscape", isLandscape);
sendEvent("namedOrientationDidChange", map);
}
private void sendEvent(String eventName, @Nullable WritableMap params) {
if (mReactInstanceManager != null) {
mReactInstanceManager.getCurrentReactContext()