Last active
May 2, 2021 18:50
-
-
Save OleksandrKucherenko/61b98244529ea5764a188d2bd4e86139 to your computer and use it in GitHub Desktop.
Helper class that help to track opening/close of virtual keyboard in activity/fragment.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Author: Oleksandr Kucherenko, 2016-present | |
package your.package.android.utils; | |
import android.annotation.SuppressLint; | |
import android.app.Activity; | |
import android.content.Context; | |
import android.graphics.Point; | |
import android.graphics.Rect; | |
import android.graphics.drawable.ColorDrawable; | |
import android.os.Build; | |
import android.os.IBinder; | |
import android.support.annotation.*; | |
import android.util.DisplayMetrics; | |
import android.view.Display; | |
import android.view.Gravity; | |
import android.view.View; | |
import android.view.ViewTreeObserver; | |
import android.view.WindowManager; | |
import android.view.inputmethod.InputMethodManager; | |
import android.widget.LinearLayout; | |
import android.widget.PopupWindow; | |
import java.lang.reflect.Method; | |
import java.util.concurrent.atomic.AtomicBoolean; | |
import java.util.concurrent.atomic.AtomicInteger; | |
import static com.google.common.base.Preconditions.checkNotNull; | |
/** Utility methods for manipulating the virtual keyboard visibility. */ | |
@SuppressWarnings("UnusedReturnValue") | |
@MainThread | |
public class KeyboardHelper { | |
/** To speed-up execution, cache the reflected method. */ | |
private static Method sVisibleHeight; | |
/** Stored height of the keyboard. */ | |
private static final AtomicInteger sKbdHeight = new AtomicInteger(); | |
/** Do we have part of the screen reserved for navigation bar or we have a hardware buttons for navigation. */ | |
private static final AtomicBoolean sHasSoftNavigation = new AtomicBoolean(); | |
/** Is Immersive already calculated and cached or not. */ | |
private static final AtomicBoolean sCachedSoftNavigation = new AtomicBoolean(); | |
/** Extract service instance from context. */ | |
@Nullable | |
@SuppressWarnings("unchecked") | |
public static <T> T service(@NonNull final Context context, @NonNull final String name) { | |
return (T) context.getSystemService(name); | |
} | |
/** Show virtual keyboard on screen. */ | |
public static boolean showKeyboard(@NonNull final Activity context) { | |
// Alternative: | |
// final Window window = context.getWindow(); | |
// window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); | |
final InputMethodManager im = get(context); | |
if (im != null) { | |
final IBinder token = context.getWindow().getDecorView().getWindowToken(); | |
im.toggleSoftInputFromWindow(token, InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); | |
return true; | |
} | |
return false; | |
} | |
/** Hide virtual keyboard if displayed one. */ | |
public static boolean hideKeyboard(@NonNull final Activity context) { | |
final InputMethodManager im = get(context); | |
if (im != null) { | |
final IBinder token = context.getWindow().getDecorView().getWindowToken(); | |
return im.hideSoftInputFromWindow(token, 0); | |
} | |
return false; | |
} | |
/** Show virtual keyboard on screen. */ | |
public static boolean showKeyboard(@NonNull final View view, @NonNull Context context) { | |
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); | |
if (imm != null) { | |
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); | |
return true; | |
} | |
return false; | |
} | |
/** Hide virtual keyboard on screen. */ | |
public static boolean hideKeyboard(@NonNull final IBinder token, @NonNull Context context) { | |
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); | |
if (imm != null) { | |
imm.hideSoftInputFromWindow(token, InputMethodManager.HIDE_NOT_ALWAYS); | |
return true; | |
} | |
return false; | |
} | |
/** Is keyboard shown to user or not. */ | |
public static boolean isShown(@NonNull final Context context) { | |
return getHeight(context) > 0; | |
} | |
/** Get Keyboard height. */ | |
public static int getHeight(@NonNull final Context context) { | |
final InputMethodManager input = get(context); | |
if (null == input) return sKbdHeight.get(); | |
try { | |
// target: API 16, v4.1, Jelly Bean | |
// method available from API 5.0.0 | |
if (null == sVisibleHeight) { | |
final Class<?> clazz = input.getClass(); | |
sVisibleHeight = clazz.getMethod("getInputMethodWindowVisibleHeight"); | |
} | |
final int height = (int) sVisibleHeight.invoke(input); | |
sKbdHeight.set(height); | |
} catch (final Throwable ignored) { | |
} | |
return sKbdHeight.get(); | |
} | |
/** Get input service instance. */ | |
@Nullable | |
public static InputMethodManager get(@NonNull final Context context) { | |
return service(context, Context.INPUT_METHOD_SERVICE); | |
} | |
/** Create instance of keyboard height tracker for older APIs. */ | |
@NonNull | |
public static HeightProvider tracker(@NonNull final Activity owner, @NonNull final View vwRoot) { | |
return new HeightProvider(owner, vwRoot); | |
} | |
/** Is navigation bar in hardware or software mode. */ | |
@SuppressLint("NewApi") | |
public static boolean hasSoftwareNavigationBar(@NonNull final Context ctx) { | |
if (!sCachedSoftNavigation.getAndSet(true)) { | |
boolean result = false; | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | |
final Display d = ((WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); | |
final DisplayMetrics realDisplayMetrics = new DisplayMetrics(); | |
final DisplayMetrics displayMetrics = new DisplayMetrics(); | |
d.getRealMetrics(realDisplayMetrics); | |
d.getMetrics(displayMetrics); | |
final int realHeight = realDisplayMetrics.heightPixels; | |
final int realWidth = realDisplayMetrics.widthPixels; | |
final int displayHeight = displayMetrics.heightPixels; | |
final int displayWidth = displayMetrics.widthPixels; | |
// is part of the screen reserved for software navigation bar?! | |
result = (realWidth > displayWidth) || (realHeight > displayHeight); | |
} | |
sHasSoftNavigation.set(result); | |
} | |
return sHasSoftNavigation.get(); | |
} | |
/** | |
* Registered window will track the keyboard and update static variable of the Keyboard Helper class. | |
* | |
* @see <a href="https://github.com/siebeprojects/samples-keyboardheight">Source of Idea</a> | |
*/ | |
public static class HeightProvider extends PopupWindow { | |
/** Reference on parent/owner activity. */ | |
private final Activity mActivity; | |
/** Reference on activity content root. */ | |
private final View mParentView; | |
/** Reference on popup view/layout. */ | |
private final View mPopupView; | |
/** Reference on listener. */ | |
private final ViewTreeObserver.OnGlobalLayoutListener mListener; | |
/** callback reference. */ | |
@Nullable | |
private Callbacks mCallback; | |
/** Major constructor. */ | |
private HeightProvider(@NonNull final Activity owner, @NonNull final View vwRoot) { | |
super(owner); | |
mActivity = owner; | |
// setup the popup | |
setContentView(mPopupView = new LinearLayout(owner)); | |
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); | |
setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); | |
setWidth(0); | |
setHeight(WindowManager.LayoutParams.MATCH_PARENT); | |
mParentView = mActivity.findViewById(android.R.id.content); | |
mListener = this::measureKbdHeight; | |
mPopupView.getViewTreeObserver().addOnGlobalLayoutListener(mListener); | |
// schedule start of HeightProvider after onPostResume processing | |
vwRoot.post(this::start); | |
} | |
/** Measure keyboard height. */ | |
/* package */ void measureKbdHeight() { | |
final Point screenSize = new Point(); | |
mActivity.getWindowManager().getDefaultDisplay().getSize(screenSize); | |
final Rect rect = new Rect(); | |
mPopupView.getWindowVisibleDisplayFrame(rect); | |
final int keyboardHeight = screenSize.y - rect.bottom; | |
final int oldHeight = sKbdHeight.getAndSet(keyboardHeight); | |
if (oldHeight != keyboardHeight && null != mCallback) { | |
mCallback.onKeyboardHeightChanged(); | |
} | |
} | |
/** Assign callback. */ | |
public void setCallback(@Nullable final Callbacks callbacks) { | |
mCallback = callbacks; | |
} | |
/** Enable popup window tracking. */ | |
/* package */ void start() { | |
// its a delayed call, activity can be destroyed | |
if (mActivity.isFinishing()) { | |
close(); | |
return; | |
} | |
setBackgroundDrawable(new ColorDrawable(0)); | |
showAtLocation(mParentView, Gravity.NO_GRAVITY, 0, 0); | |
} | |
/** close popup window. */ | |
public void close() { | |
mParentView.getViewTreeObserver().removeOnGlobalLayoutListener(mListener); | |
dismiss(); | |
} | |
} | |
/** Callback for tracking keyboard height changes. */ | |
public interface Callbacks { | |
/** Triggered when detected keyboard height change. */ | |
void onKeyboardHeightChanged(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
App styles:
values-v19/styles.xml
:values-v21/styles.xml
: