1. 程式人生 > 程式設計 >Android監聽鍵盤狀態獲取鍵盤高度的實現方法

Android監聽鍵盤狀態獲取鍵盤高度的實現方法

前言

Android暫時還沒有提供一個合適的API來獲取/監聽鍵盤的狀態和高度,而我們又經常會有這個需求.

最近我的一個專案中,在ugc頁面需要在鍵盤頂部,緊貼著鍵盤顯示一個文字提示,當鍵盤消失時就隱藏.
因此,我需要監聽軟鍵盤的開啟/關閉,以及獲取它的高度.

ViewTreeObserver

A view tree observer is used to register listeners that can be notified of global changes in the view tree. Such global events include,but are not limited to,layout of the whole tree,beginning of the drawing pass,touch mode change…

Android框架提供了一個ViewTreeObserver類,它是一個View檢視樹的觀察者類。ViewTreeObserver類中定義了一系列的公共介面(public interface)。當一個View attach到一個視窗上時就會建立一個ViewTreeObserver物件,這樣當一個View的檢視樹發生改變時,就會呼叫該物件的某個方法,將事件通知給每個註冊的監聽者。

OnGlobalLayoutListener是ViewTreeObserver中定義的眾多介面中的一個,它用來監聽一個檢視樹中全域性佈局的改變或者檢視樹中的某個檢視的可視狀態的改變。當軟鍵盤由隱藏變為顯示,或由顯示變為隱藏時,都會呼叫當前佈局中所有存在的View中的ViewTreeObserver物件的dispatchOnGlobalLayout()方法,此方法中會遍歷所有已註冊的OnGlobalLayoutListener,執行相應的回撥方法,將全域性佈局改變的訊息通知給每個註冊的監聽者。

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()會返回視窗的可見區域高度,通過和螢幕高度相減,就可以得到軟鍵盤的高度了。

完整示例程式碼

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;

 //使用時用這個構造方法
 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 可能是包含狀態列的高度和底部虛擬按鍵的高度
  */
 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);
 }
}

可以看到,我建了一個自己的一個Listener,通過這個listener實現我們想要的監聽,然後在這裡處理一些邏輯問題.

主要程式碼還是在onGlobalLayout中:

首先通過activityRootView.getWindowVisibleDisplayFrame(r)檢索此檢視所附加的視窗所在的整個可見顯示大小,然後減去,已顯示的檢視的高度,(r.bottom - r.top)就是顯示的view的下座標和上座標,差即為高度.

至此,我們得到了剩餘的高度 . 這個高度可能就是鍵盤高度了,為什麼說可能呢?因為還麼有考慮到頂部的狀態列和底部的虛擬導航欄. 當然也可能不是鍵盤.

然後我們根據這個高度和之前已知的鍵盤狀態來判斷是否為鍵盤.
並回調給監聽者.

使用

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,keyboardHeightInPx
          - ScreenUtils.getStatusHeight(UGCEditActivity.this)
          - ScreenUtils.getBottomStatusHeight(UGCEditActivity.this));
     }

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

Scrollview是整個頁面的根佈局,我通過監聽它來實現對整個佈局的監聽.

mError1000tv就是我一開始提到的要緊貼鍵盤頂部顯示的一個textview了.

我通過LayoutParams給它設定邊距,只設置了底部邊距,值為返回的"鍵盤高度"- 頂部狀態列高度-虛擬導航欄的高度. 得到真實的鍵盤高度.

在onSoftKeyboardOpened和onSoftKeyboardClosed這兩個回撥中,處理自己的邏輯就好了.

然後放上我這邊螢幕工具類ScreenUtils的程式碼,需要的可以複製下來

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");
 }

 /**
  * 獲得螢幕高度
  *
  * @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;
 }

 /**
  * 獲得螢幕寬度
  *
  * @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;
 }

 /**
  * 獲得狀態列的高度
  *
  * @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;
 }

 /**
  * 獲取當前螢幕截圖,包含狀態列
  *
  * @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,width,height);
  view.destroyDrawingCache();
  return bp;

 }

 /**
  * 獲取當前螢幕截圖,不包含狀態列
  *
  * @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,statusBarHeight,height
    - statusBarHeight);
  view.destroyDrawingCache();
  return bp;
 }

 /**
  * 獲取 虛擬按鍵的高度
  *
  * @param context 上下文
  * @return 虛擬鍵高度
  */
 public static int getBottomStatusHeight(Context context) {
  int totalHeight = getAbsoluteHeight(context);

  int contentHeight = getScreenHeight(context);

  return totalHeight - contentHeight;
 }

 /**
  * 獲取螢幕原始尺寸高度,包括虛擬功能鍵高度
  *
  * @param 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;
 }
}

全文完.

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對我們的支援。