Skip to content

Commit

Permalink
feat!(keyboard): Improvements to keyboard show / hide events (#1980)
Browse files Browse the repository at this point in the history
Co-authored-by: Mark Anderson <mark@ionic.io>
  • Loading branch information
theproducer and markemer committed Jan 23, 2024
1 parent bc61f5c commit c4f9165
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 107 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package com.capacitorjs.plugins.keyboard;

import android.content.Context;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.getcapacitor.Logger;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.List;

public class Keyboard {

Expand All @@ -24,16 +22,10 @@ interface KeyboardEventListener {
}

private AppCompatActivity activity;
private ViewTreeObserver.OnGlobalLayoutListener list;
private View rootView;
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;

@Nullable
public KeyboardEventListener getKeyboardEventListener() {
return keyboardEventListener;
}
private View mChildOfContent;

public void setKeyboardEventListener(@Nullable KeyboardEventListener keyboardEventListener) {
this.keyboardEventListener = keyboardEventListener;
Expand All @@ -49,117 +41,69 @@ public void setKeyboardEventListener(@Nullable KeyboardEventListener keyboardEve

public Keyboard(AppCompatActivity activity, boolean resizeOnFullScreen) {
this.activity = activity;
//calculate density-independent pixels (dp)
//http://developer.android.com/guide/practices/screens_support.html
DisplayMetrics dm = activity.getResources().getDisplayMetrics();
final float density = dm.density;

//http://stackoverflow.com/a/4737265/1091751 detect if keyboard is showing
FrameLayout content = activity.getWindow().getDecorView().findViewById(android.R.id.content);
rootView = content.getRootView();
list =
new ViewTreeObserver.OnGlobalLayoutListener() {
int previousHeightDiff = 0;

ViewCompat.setWindowInsetsAnimationCallback(
rootView,
new WindowInsetsAnimationCompat.Callback(WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP) {
@NonNull
@Override
public void onGlobalLayout() {
Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
rootView.getWindowVisibleDisplayFrame(r);

// cache properties for later use
int rootViewHeight = rootView.getRootView().getHeight();
int resultBottom = r.bottom;
int screenHeight;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Insets windowInsets = rootView.getRootWindowInsets().getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
screenHeight = rootViewHeight;
resultBottom = resultBottom + windowInsets.bottom;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
WindowInsets windowInsets = rootView.getRootWindowInsets();
int stableInsetBottom = getLegacyStableInsetBottom(windowInsets);
screenHeight = rootViewHeight;
resultBottom = resultBottom + stableInsetBottom;
} else {
Point size = getLegacySizePoint();
screenHeight = size.y;
}
public WindowInsetsCompat onProgress(
@NonNull WindowInsetsCompat insets,
@NonNull List<WindowInsetsAnimationCompat> runningAnimations
) {
return insets;
}

int heightDiff = screenHeight - resultBottom;

int pixelHeightDiff = (int) (heightDiff / density);

if (pixelHeightDiff > 100 && pixelHeightDiff != previousHeightDiff) { // if more than 100 pixels, its probably a keyboard...
if (resizeOnFullScreen) {
possiblyResizeChildOfContent(true);
}

if (keyboardEventListener != null) {
keyboardEventListener.onKeyboardEvent(EVENT_KB_WILL_SHOW, pixelHeightDiff);
keyboardEventListener.onKeyboardEvent(EVENT_KB_DID_SHOW, pixelHeightDiff);
} else {
Logger.warn("Native Keyboard Event Listener not found");
}
} else if (pixelHeightDiff != previousHeightDiff && (previousHeightDiff - pixelHeightDiff) > 100) {
if (resizeOnFullScreen) {
possiblyResizeChildOfContent(false);
}

if (keyboardEventListener != null) {
keyboardEventListener.onKeyboardEvent(EVENT_KB_WILL_HIDE, 0);
keyboardEventListener.onKeyboardEvent(EVENT_KB_DID_HIDE, 0);
} else {
Logger.warn("Native Keyboard Event Listener not found");
}
@NonNull
@Override
public WindowInsetsAnimationCompat.BoundsCompat onStart(
@NonNull WindowInsetsAnimationCompat animation,
@NonNull WindowInsetsAnimationCompat.BoundsCompat bounds
) {
boolean showingKeyboard = ViewCompat.getRootWindowInsets(rootView).isVisible(WindowInsetsCompat.Type.ime());
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(rootView);
int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
DisplayMetrics dm = activity.getResources().getDisplayMetrics();
final float density = dm.density;

if (resizeOnFullScreen) {
possiblyResizeChildOfContent(showingKeyboard);
}
previousHeightDiff = pixelHeightDiff;
}

private void possiblyResizeChildOfContent(boolean keyboardShown) {
int usableHeightNow = keyboardShown ? computeUsableHeight() : -1;
if (usableHeightPrevious != usableHeightNow) {
frameLayoutParams.height = usableHeightNow;
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
if (showingKeyboard) {
keyboardEventListener.onKeyboardEvent(EVENT_KB_WILL_SHOW, Math.round(imeHeight / density));
} else {
keyboardEventListener.onKeyboardEvent(EVENT_KB_WILL_HIDE, 0);
}
return super.onStart(animation, bounds);
}

private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return isOverlays() ? r.bottom : r.height();
@Override
public void onEnd(@NonNull WindowInsetsAnimationCompat animation) {
super.onEnd(animation);
boolean showingKeyboard = ViewCompat.getRootWindowInsets(rootView).isVisible(WindowInsetsCompat.Type.ime());
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(rootView);
int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
DisplayMetrics dm = activity.getResources().getDisplayMetrics();
final float density = dm.density;

if (showingKeyboard) {
keyboardEventListener.onKeyboardEvent(EVENT_KB_DID_SHOW, Math.round(imeHeight / density));
} else {
keyboardEventListener.onKeyboardEvent(EVENT_KB_DID_HIDE, 0);
}
}
}
);

@SuppressWarnings("deprecation")
private boolean isOverlays() {
final Window window = activity.getWindow();
return (
(window.getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) ==
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
);
}
};
mChildOfContent = content.getChildAt(0);
rootView.getViewTreeObserver().addOnGlobalLayoutListener(list);
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}

@SuppressWarnings("deprecation")
private int getLegacyStableInsetBottom(WindowInsets windowInsets) {
return windowInsets.getStableInsetBottom();
}

@SuppressWarnings("deprecation")
private Point getLegacySizePoint() {
// calculate screen height differently for android versions <23: Lollipop 5.x, Marshmallow 6.x
//http://stackoverflow.com/a/29257533/3642890 beware of nexus 5
Display display = activity.getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size;
}

public void show() {
((InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(activity.getCurrentFocus(), 0);
}
Expand All @@ -174,4 +118,27 @@ public boolean hide() {
return true;
}
}

private void possiblyResizeChildOfContent(boolean keyboardShown) {
int usableHeightNow = keyboardShown ? computeUsableHeight() : -1;
if (usableHeightPrevious != usableHeightNow) {
frameLayoutParams.height = usableHeightNow;
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}

private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return isOverlays() ? r.bottom : r.height();
}

@SuppressWarnings("deprecation")
private boolean isOverlays() {
final Window window = activity.getWindow();
return (
(window.getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public void load() {
() -> {
boolean resizeOnFullScreen = getConfig().getBoolean("resizeOnFullScreen", false);
implementation = new Keyboard(getActivity(), resizeOnFullScreen);

implementation.setKeyboardEventListener(this::onKeyboardEvent);
}
);
Expand Down

0 comments on commit c4f9165

Please sign in to comment.