mirror of
https://github.com/status-im/react-native.git
synced 2025-02-26 08:05:34 +00:00
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:
parent
5d240a8ed3
commit
f07ca31303
@ -23,7 +23,9 @@
|
||||
android:theme="@style/Theme.ReactNative.AppCompat.Light" >
|
||||
<activity
|
||||
android:name=".UIExplorerActivity"
|
||||
android:label="@string/app_name" >
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="fullSensor"
|
||||
android:configChanges="orientation|screenSize" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
80
Examples/UIExplorer/js/OrientationChangeExample.js
Normal file
80
Examples/UIExplorer/js/OrientationChangeExample.js
Normal file
@ -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 (
|
||||
<View>
|
||||
<Text>{JSON.stringify(this.state)}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
exports.title = 'OrientationChangeExample';
|
||||
exports.description = 'listening to orientation changes';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'OrientationChangeExample',
|
||||
description: 'listening to device orientation changes',
|
||||
render() { return <OrientationChangeExample />; },
|
||||
},
|
||||
];
|
@ -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<UIExplorerExample> = [
|
||||
const ComponentExamples: Array<UIExplorerExample> = [
|
||||
{
|
||||
key: 'ActivityIndicatorExample',
|
||||
module: require('./ActivityIndicatorExample'),
|
||||
@ -108,7 +106,7 @@ var ComponentExamples: Array<UIExplorerExample> = [
|
||||
},
|
||||
];
|
||||
|
||||
const APIExamples = [
|
||||
const APIExamples: Array<UIExplorerExample> = [
|
||||
{
|
||||
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'),
|
||||
|
@ -23,8 +23,8 @@
|
||||
'use strict';
|
||||
|
||||
export type UIExplorerExample = {
|
||||
key: string;
|
||||
module: Object;
|
||||
key: string,
|
||||
module: Object,
|
||||
};
|
||||
|
||||
const ComponentExamples: Array<UIExplorerExample> = [
|
||||
@ -235,6 +235,10 @@ const APIExamples: Array<UIExplorerExample> = [
|
||||
key: 'NetInfoExample',
|
||||
module: require('./NetInfoExample'),
|
||||
},
|
||||
{
|
||||
key: 'OrientationChangeExample',
|
||||
module: require('./OrientationChangeExample'),
|
||||
},
|
||||
{
|
||||
key: 'PanResponderExample',
|
||||
module: require('./PanResponderExample'),
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user