Implementation method of Android monitoring keyboard state to obtain keyboard height

  • 2021-11-14 07:01:11
  • OfStack

Preface

Android has not yet provided a suitable API to obtain/monitor the status and height of the keyboard, and we often have this requirement.

In one of my recent projects, the ugc page needed to display a text prompt at the top of the keyboard, close to the keyboard, and hide it when the keyboard disappeared.
Therefore, I need to monitor the opening/closing of the soft keyboard and get its height.

ViewTreeObserver

A view tree observer is used to register listeners that be notified of in the the tree Such global events include, but are not to, layout of whole tree, beginning of the drawing touch touch

The Android framework provides an ViewTreeObserver class, which is an observer class for an View view tree. Family 1 common interfaces (public interface) are defined in the ViewTreeObserver class. When an View attach is placed on a window, an ViewTreeObserver object is created, so that when the view tree of an View changes, a method of that object is called to notify each registered listener of the event.

OnGlobalLayoutListener is one of many interfaces defined in ViewTreeObserver, which is used to listen for changes in the global layout in a view tree or changes in the visual state of a view in the view tree. When the soft keyboard changes from hidden to displayed, or from displayed to hidden, the dispatchOnGlobalLayout () method of ViewTreeObserver object in all View existing in the current layout will be called. In this method, all registered OnGlobalLayoutListener will be traversed, and the corresponding callback method will be executed to inform each registered listener of the global layout change message.


view.getViewTreeObserver().addOnGlobalLayoutListener(listener);

getWindowVisibleDisplayFrame

Retrieve the overall visible display size in which the window this view is attached to has been positioned in.

getWindowVisibleDisplayFrame () returns the height of the visible area of the window, which is subtracted from the height of the screen to get the height of the soft keyboard.

Complete sample code


package com.cari.cari.promo.diskon.util;


import android.content.Context;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewTreeObserver;

import java.util.LinkedList;
import java.util.List;

public class SoftKeyboardStateWatcher implements ViewTreeObserver.OnGlobalLayoutListener {

 public interface SoftKeyboardStateListener {
  void onSoftKeyboardOpened(int keyboardHeightInPx);

  void onSoftKeyboardClosed();
 }

 private final List<SoftKeyboardStateListener> listeners = new LinkedList<>();
 private final View activityRootView;
 private int lastSoftKeyboardHeightInPx;
 private boolean isSoftKeyboardOpened;
 private Context mContext;

 // Use this constructor when using 
 public SoftKeyboardStateWatcher(View activityRootView, Context context) {

  this(activityRootView, false);
  this.mContext = context;
 }

 private SoftKeyboardStateWatcher(View activityRootView) {
  this(activityRootView, false);
 }

 private SoftKeyboardStateWatcher(View activityRootView, boolean isSoftKeyboardOpened) {
  this.activityRootView = activityRootView;
  this.isSoftKeyboardOpened = isSoftKeyboardOpened;
  activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(this);
 }

 @Override
 public void onGlobalLayout() {
  final Rect r = new Rect();
  activityRootView.getWindowVisibleDisplayFrame(r);

  final int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
  if (!isSoftKeyboardOpened && heightDiff > dpToPx(mContext, 200)) {
   isSoftKeyboardOpened = true;
   notifyOnSoftKeyboardOpened(heightDiff);
  } else if (isSoftKeyboardOpened && heightDiff < dpToPx(mContext, 200)) {
   isSoftKeyboardOpened = false;
   notifyOnSoftKeyboardClosed();
  }
 }

 public void setIsSoftKeyboardOpened(boolean isSoftKeyboardOpened) {
  this.isSoftKeyboardOpened = isSoftKeyboardOpened;
 }

 public boolean isSoftKeyboardOpened() {
  return isSoftKeyboardOpened;
 }

 /**
  * Default value is zero {@code 0}.
  *
  * @return last saved keyboard height in px
  */
 public int getLastSoftKeyboardHeightInPx() {
  return lastSoftKeyboardHeightInPx;
 }

 public void addSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
  listeners.add(listener);
 }

 public void removeSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
  listeners.remove(listener);
 }

 /**
  * @param keyboardHeightInPx  It may be the height of the containing status bar and the height of the bottom virtual key 
  */
 private void notifyOnSoftKeyboardOpened(int keyboardHeightInPx) {
  this.lastSoftKeyboardHeightInPx = keyboardHeightInPx;

  for (SoftKeyboardStateListener listener : listeners) {
   if (listener != null) {
    listener.onSoftKeyboardOpened(keyboardHeightInPx);
   }
  }
 }

 private void notifyOnSoftKeyboardClosed() {
  for (SoftKeyboardStateListener listener : listeners) {
   if (listener != null) {
    listener.onSoftKeyboardClosed();
   }
  }
 }

 private static float dpToPx(Context context, float valueInDp) {
  DisplayMetrics metrics = context.getResources().getDisplayMetrics();
  return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, valueInDp, metrics);
 }
}

As you can see, I built my own Listener, and through this listener, I realized the listening we wanted, and then I handled some logic problems here.

The main code is still in onGlobalLayout:

Pass first activityRootView.getWindowVisibleDisplayFrame(r) Retrieves the entire visible display size of the window attached to this view, and subtracts the height of the displayed view (r. bottom-r. top), which is the lower and upper coordinates of the displayed view, and the difference is the height.

At this point, we have the remaining height. This height may be the keyboard height. Why is it possible? Because the status bar at the top and the virtual navigation bar at the bottom have not been considered. Of course, it may not be the keyboard.

Then we judge whether it is a keyboard according to this height and the previously known keyboard state.
And call back to the listener.

Use


ScrollView scrollView = findViewById(R.id.ugc_scrollview);
  final SoftKeyboardStateWatcher watcher = new SoftKeyboardStateWatcher(scrollView, this);
  watcher.addSoftKeyboardStateListener(
    new SoftKeyboardStateWatcher.SoftKeyboardStateListener() {

     @Override
     public void onSoftKeyboardOpened(int keyboardHeightInPx) {
      ConstraintLayout.LayoutParams layoutParamsVideo = (ConstraintLayout.LayoutParams) mError1000tv.getLayoutParams();

      layoutParamsVideo.setMargins(0,
        0,
        0,
        keyboardHeightInPx
          - ScreenUtils.getStatusHeight(UGCEditActivity.this)
          - ScreenUtils.getBottomStatusHeight(UGCEditActivity.this));
     }

     @Override
     public void onSoftKeyboardClosed() {
      mError1000tv.setVisibility(View.GONE);
     }
    }
  );

Scrollview is the root layout of the entire page, and I monitor the entire layout by listening to it.

mError1000tv is the textview I mentioned at the beginning of 1 to be displayed close to the top of the keyboard.

I set the margin for it through LayoutParams, only the bottom margin is set, and the value is returned "keyboard height"-top status bar height-virtual navigation bar height. Get the real keyboard height.

In the onSoftKeyboardOpened and onSoftKeyboardClosed callbacks, it's good to handle your own logic.

Then put on my side of the screen tool class ScreenUtils code, the need can be copied down

ScreenUtils


package com.cari.promo.diskon.util;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;

import java.lang.reflect.Method;

public class ScreenUtils {
 private ScreenUtils() {
  /* cannot be instantiated */
  throw new UnsupportedOperationException("cannot be instantiated");
 }

 /**
  *  Get screen height 
  *
  * @param context
  * @return
  */
 public static int getScreenWidth(Context context) {
  WindowManager wm = (WindowManager) context
    .getSystemService(Context.WINDOW_SERVICE);
  DisplayMetrics outMetrics = new DisplayMetrics();
  wm.getDefaultDisplay().getMetrics(outMetrics);
  return outMetrics.widthPixels;
 }

 /**
  *  Get screen width 
  *
  * @param context
  * @return
  */
 public static int getScreenHeight(Context context) {
  WindowManager wm = (WindowManager) context
    .getSystemService(Context.WINDOW_SERVICE);
  DisplayMetrics outMetrics = new DisplayMetrics();
  wm.getDefaultDisplay().getMetrics(outMetrics);
  return outMetrics.heightPixels;
 }

 /**
  *  Get the height of the status bar 
  *
  * @param context
  * @return
  */
 public static int getStatusHeight(Context context) {

  int statusHeight = -1;
  try {
   Class<?> clazz = Class.forName("com.android.internal.R$dimen");
   Object object = clazz.newInstance();
   int height = Integer.parseInt(clazz.getField("status_bar_height")
     .get(object).toString());
   statusHeight = context.getResources().getDimensionPixelSize(height);
  } catch (Exception e) {
   e.printStackTrace();
  }
  return statusHeight;
 }

 /**
  *  Get a screenshot of the current screen, including the status bar 
  *
  * @param activity
  * @return
  */
 public static Bitmap snapShotWithStatusBar(Activity activity) {
  View view = activity.getWindow().getDecorView();
  view.setDrawingCacheEnabled(true);
  view.buildDrawingCache();
  Bitmap bmp = view.getDrawingCache();
  int width = getScreenWidth(activity);
  int height = getScreenHeight(activity);
  Bitmap bp = null;
  bp = Bitmap.createBitmap(bmp, 0, 0, width, height);
  view.destroyDrawingCache();
  return bp;

 }

 /**
  *  Get a screenshot of the current screen without the status bar 
  *
  * @param activity
  * @return
  */
 public static Bitmap snapShotWithoutStatusBar(Activity activity) {
  View view = activity.getWindow().getDecorView();
  view.setDrawingCacheEnabled(true);
  view.buildDrawingCache();
  Bitmap bmp = view.getDrawingCache();
  Rect frame = new Rect();
  activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
  int statusBarHeight = frame.top;

  int width = getScreenWidth(activity);
  int height = getScreenHeight(activity);
  Bitmap bp = null;
  bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height
    - statusBarHeight);
  view.destroyDrawingCache();
  return bp;
 }

 /**
  *  Get   Height of virtual keys 
  *
  * @param context  Context 
  * @return  Virtual key height 
  */
 public static int getBottomStatusHeight(Context context) {
  int totalHeight = getAbsoluteHeight(context);

  int contentHeight = getScreenHeight(context);

  return totalHeight - contentHeight;
 }

 /**
  *  Get the original size height of the screen, including the height of virtual function keys 
  *
  * @param context  Context 
  * @return The absolute height of the available display size in pixels.
  */
 private static int getAbsoluteHeight(Context context) {
  int absoluteHeight = 0;
  WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  Display display = null;
  if (windowManager != null) {
   display = windowManager.getDefaultDisplay();
  }
  DisplayMetrics displayMetrics = new DisplayMetrics();
  @SuppressWarnings("rawtypes")
  Class c;
  try {
   c = Class.forName("android.view.Display");
   @SuppressWarnings("unchecked")
   Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
   method.invoke(display, displayMetrics);
   absoluteHeight = displayMetrics.heightPixels;
  } catch (Exception e) {
   e.printStackTrace();
  }
  return absoluteHeight;
 }
}

End of the full text.

Summarize


Related articles: