diff --git a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml index ab2757720..bd77d77e4 100644 --- a/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml +++ b/Examples/UIExplorer/android/app/src/main/AndroidManifest.xml @@ -23,7 +23,9 @@ android:theme="@style/Theme.ReactNative.AppCompat.Light" > + android:label="@string/app_name" + android:screenOrientation="fullSensor" + android:configChanges="orientation|screenSize" > diff --git a/Examples/UIExplorer/js/OrientationChangeExample.js b/Examples/UIExplorer/js/OrientationChangeExample.js new file mode 100644 index 000000000..1bcb767c7 --- /dev/null +++ b/Examples/UIExplorer/js/OrientationChangeExample.js @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @providesModule OrientationChangeExample + * @flow + */ +'use strict'; + +const React = require('react'); +const ReactNative = require('react-native'); +const { + DeviceEventEmitter, + Text, + View, +} = ReactNative; + +import type EmitterSubscription from 'EmitterSubscription'; + +class OrientationChangeExample extends React.Component { + _orientationSubscription: EmitterSubscription; + + state = { + currentOrientation: '', + orientationDegrees: 0, + isLandscape: false, + }; + + componentDidMount() { + this._orientationSubscription = DeviceEventEmitter.addListener( + 'namedOrientationDidChange', this._onOrientationChange, + ); + } + + componentWillUnmount() { + this._orientationSubscription.remove(); + } + + _onOrientationChange = (orientation: Object) => { + this.setState({ + currentOrientation: orientation.name, + orientationDegrees: orientation.rotationDegrees, + isLandscape: orientation.isLandscape, + }); + } + + render() { + return ( + + {JSON.stringify(this.state)} + + ); + } +} + +exports.title = 'OrientationChangeExample'; +exports.description = 'listening to orientation changes'; +exports.examples = [ + { + title: 'OrientationChangeExample', + description: 'listening to device orientation changes', + render() { return ; }, + }, +]; diff --git a/Examples/UIExplorer/js/UIExplorerList.android.js b/Examples/UIExplorer/js/UIExplorerList.android.js index 2d1693172..d276dea62 100644 --- a/Examples/UIExplorer/js/UIExplorerList.android.js +++ b/Examples/UIExplorer/js/UIExplorerList.android.js @@ -22,14 +22,12 @@ */ 'use strict'; -const React = require('React'); - export type UIExplorerExample = { - key: string; - module: React.Component; + key: string, + module: Object, }; -var ComponentExamples: Array = [ +const ComponentExamples: Array = [ { key: 'ActivityIndicatorExample', module: require('./ActivityIndicatorExample'), @@ -108,7 +106,7 @@ var ComponentExamples: Array = [ }, ]; -const APIExamples = [ +const APIExamples: Array = [ { key: 'AccessibilityAndroidExample', module: require('./AccessibilityAndroidExample'), @@ -177,6 +175,10 @@ const APIExamples = [ key: 'NetInfoExample', module: require('./NetInfoExample'), }, + { + key: 'OrientationChangeExample', + module: require('./OrientationChangeExample'), + }, { key: 'PanResponderExample', module: require('./PanResponderExample'), diff --git a/Examples/UIExplorer/js/UIExplorerList.ios.js b/Examples/UIExplorer/js/UIExplorerList.ios.js index ecbb0f44e..55648b6dc 100644 --- a/Examples/UIExplorer/js/UIExplorerList.ios.js +++ b/Examples/UIExplorer/js/UIExplorerList.ios.js @@ -23,8 +23,8 @@ 'use strict'; export type UIExplorerExample = { - key: string; - module: Object; + key: string, + module: Object, }; const ComponentExamples: Array = [ @@ -235,6 +235,10 @@ const APIExamples: Array = [ key: 'NetInfoExample', module: require('./NetInfoExample'), }, + { + key: 'OrientationChangeExample', + module: require('./OrientationChangeExample'), + }, { key: 'PanResponderExample', module: require('./PanResponderExample'), diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index d6ae11913..3c8cde460 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -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()