Skip to content

Commit 1e48274

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
Use WindowInsetsCompat for Keyboard Events
Summary: RN for Android fires `keyboardDidShow` and `keyboardDidHide` by observing changes to viewable window size. This isn't always reliable, such as when an activity has `awindowSoftInputMode` set to not have the system adjust the viewport when a keyboard is opened. Android 11 added the direct ability to measure and check visibility of the soft keyboard via `WindowInsets`, which fixes these issues. This is exposed downlevel to API 23 via WindowInsetsComapt` with the same limitations as previously, but using it simplifies our calculations a lot. Changelog: [Android][Fixed] - Use WindowInsetsCompat for Keyboard Events Reviewed By: javache Differential Revision: D38500859 fbshipit-source-id: d4ad41d7e75e4b9c14a485539a5f9de19de74362
1 parent a379879 commit 1e48274

File tree

2 files changed

+69
-7
lines changed

2 files changed

+69
-7
lines changed

Libraries/Components/Keyboard/Keyboard.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,11 @@ class Keyboard {
126126
* - `keyboardWillChangeFrame`
127127
* - `keyboardDidChangeFrame`
128128
*
129-
* Note that if you set `android:windowSoftInputMode` to `adjustResize` or `adjustNothing`,
130-
* only `keyboardDidShow` and `keyboardDidHide` events will be available on Android.
131-
* `keyboardWillShow` as well as `keyboardWillHide` are generally not available on Android
132-
* since there is no native corresponding event.
129+
* Android versions prior to API 30 rely on observing layout changes when
130+
* `android:windowSoftInputMode` is set to `adjustResize` or `adjustPan`.
131+
*
132+
* `keyboardWillShow` as well as `keyboardWillHide` are not available on Android since there is
133+
* no native corresponding event.
133134
*
134135
* @param {function} callback function to be called when the event fires.
135136
*/

ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import static com.facebook.react.uimanager.common.UIManagerType.FABRIC;
1313
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;
1414

15+
import android.app.Activity;
1516
import android.content.Context;
1617
import android.graphics.Canvas;
1718
import android.graphics.Point;
@@ -31,6 +32,9 @@
3132
import android.view.WindowManager;
3233
import android.widget.FrameLayout;
3334
import androidx.annotation.Nullable;
35+
import androidx.annotation.RequiresApi;
36+
import androidx.core.graphics.Insets;
37+
import androidx.core.view.WindowInsetsCompat;
3438
import com.facebook.common.logging.FLog;
3539
import com.facebook.infer.annotation.Assertions;
3640
import com.facebook.infer.annotation.ThreadConfined;
@@ -770,7 +774,11 @@ public void runApplication() {
770774

771775
@VisibleForTesting
772776
/* package */ void simulateCheckForKeyboardForTesting() {
773-
getCustomGlobalLayoutListener().checkForKeyboardEvents();
777+
if (Build.VERSION.SDK_INT >= 23) {
778+
getCustomGlobalLayoutListener().checkForKeyboardEvents();
779+
} else {
780+
getCustomGlobalLayoutListener().checkForKeyboardEventsLegacy();
781+
}
774782
}
775783

776784
private CustomGlobalLayoutListener getCustomGlobalLayoutListener() {
@@ -879,7 +887,8 @@ private class CustomGlobalLayoutListener implements ViewTreeObserver.OnGlobalLay
879887
private final Rect mVisibleViewArea;
880888
private final int mMinKeyboardHeightDetected;
881889

882-
private int mKeyboardHeight = 0;
890+
private boolean mKeyboardIsVisible = false;
891+
private int mKeyboardHeight = 0; // Only used in checkForKeyboardEventsLegacy path
883892
private int mDeviceRotation = 0;
884893

885894
/* package */ CustomGlobalLayoutListener() {
@@ -895,13 +904,62 @@ public void onGlobalLayout() {
895904
|| mReactInstanceManager.getCurrentReactContext() == null) {
896905
return;
897906
}
898-
checkForKeyboardEvents();
907+
908+
// WindowInsetsCompat IME measurement is reliable for API level 23+.
909+
// https://developer.android.com/jetpack/androidx/releases/core#1.5.0-alpha02
910+
if (Build.VERSION.SDK_INT >= 23) {
911+
checkForKeyboardEvents();
912+
} else {
913+
checkForKeyboardEventsLegacy();
914+
}
915+
899916
checkForDeviceOrientationChanges();
900917
checkForDeviceDimensionsChanges();
901918
}
902919

920+
@RequiresApi(api = Build.VERSION_CODES.M)
903921
private void checkForKeyboardEvents() {
904922
getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);
923+
WindowInsets rootInsets = getRootView().getRootWindowInsets();
924+
WindowInsetsCompat compatRootInsets = WindowInsetsCompat.toWindowInsetsCompat(rootInsets);
925+
926+
boolean keyboardIsVisible = compatRootInsets.isVisible(WindowInsetsCompat.Type.ime());
927+
if (keyboardIsVisible != mKeyboardIsVisible) {
928+
mKeyboardIsVisible = keyboardIsVisible;
929+
930+
if (keyboardIsVisible) {
931+
Insets imeInsets = compatRootInsets.getInsets(WindowInsetsCompat.Type.ime());
932+
Insets barInsets = compatRootInsets.getInsets(WindowInsetsCompat.Type.systemBars());
933+
int height = imeInsets.bottom - barInsets.bottom;
934+
935+
int softInputMode = ((Activity) getContext()).getWindow().getAttributes().softInputMode;
936+
int screenY =
937+
softInputMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
938+
? mVisibleViewArea.bottom - height
939+
: mVisibleViewArea.bottom;
940+
941+
sendEvent(
942+
"keyboardDidShow",
943+
createKeyboardEventPayload(
944+
PixelUtil.toDIPFromPixel(screenY),
945+
PixelUtil.toDIPFromPixel(mVisibleViewArea.left),
946+
PixelUtil.toDIPFromPixel(mVisibleViewArea.width()),
947+
PixelUtil.toDIPFromPixel(height)));
948+
} else {
949+
sendEvent(
950+
"keyboardDidHide",
951+
createKeyboardEventPayload(
952+
PixelUtil.toDIPFromPixel(mLastHeight),
953+
0,
954+
PixelUtil.toDIPFromPixel(mVisibleViewArea.width()),
955+
0));
956+
}
957+
}
958+
}
959+
960+
private void checkForKeyboardEventsLegacy() {
961+
getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);
962+
905963
int notchHeight = 0;
906964
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
907965
WindowInsets insets = getRootView().getRootWindowInsets();
@@ -919,8 +977,10 @@ private void checkForKeyboardEvents() {
919977

920978
boolean isKeyboardShowingOrKeyboardHeightChanged =
921979
mKeyboardHeight != heightDiff && heightDiff > mMinKeyboardHeightDetected;
980+
922981
if (isKeyboardShowingOrKeyboardHeightChanged) {
923982
mKeyboardHeight = heightDiff;
983+
mKeyboardIsVisible = true;
924984
sendEvent(
925985
"keyboardDidShow",
926986
createKeyboardEventPayload(
@@ -934,6 +994,7 @@ private void checkForKeyboardEvents() {
934994
boolean isKeyboardHidden = mKeyboardHeight != 0 && heightDiff <= mMinKeyboardHeightDetected;
935995
if (isKeyboardHidden) {
936996
mKeyboardHeight = 0;
997+
mKeyboardIsVisible = false;
937998
sendEvent(
938999
"keyboardDidHide",
9391000
createKeyboardEventPayload(

0 commit comments

Comments
 (0)