1. 程式人生 > >Android中獲取軟鍵盤狀態和軟鍵盤高度

Android中獲取軟鍵盤狀態和軟鍵盤高度

應用場景

在Android應用中有時會需要獲取軟鍵盤的狀態(即軟鍵盤是顯示還是隱藏)和軟鍵盤的高度。這裡列舉了一些可能的應用場景。

  1. 場景一
    當軟鍵盤顯示時,按下返回鍵應當是收起軟鍵盤,而不是回退到上一個介面,但部分機型在返回鍵處理上有bug,按下返回鍵後,雖然軟鍵盤會自動收起,但不會消費返回事件,導致Activity還會收到這次返回事件,執行回退操作,這時就需要判斷,如果軟鍵盤剛剛由顯示變為隱藏狀態,就不執行回退操作。
  2. 場景二
    當軟鍵盤彈出後,會將介面底部到中間的一大部分全部擋住,如果使用者要檢視、操作被覆蓋的區域,必須先收起軟鍵盤,這會影響使用者互動。所以通常需要在軟鍵盤彈出後,將底部的一些View,例如Button,移到軟鍵盤的上方,方便使用者操作。

API的困境

Android SDK中沒有提供任何API來直接獲取軟鍵盤的狀態和軟鍵盤的高度,網上很多資料說InputMethodManager的isActive()方法可以獲取軟鍵盤狀態,不過實際測試發現,這個方法並沒有什麼用,如果它返回false,可以判斷軟鍵盤一定是隱藏的,但如果它返回true,軟鍵盤既可能是顯示的,也可能是隱藏的。所以並不能通過isActive()方法來判斷軟鍵盤究竟是顯示還是隱藏的。要想獲取軟鍵盤的狀態和軟鍵盤的高度,只能通過間接方法實現。

註冊佈局變化監聽

在Android中當軟鍵盤由隱藏變為顯示,或由顯示變為隱藏時,會觸發當前佈局中View的全域性佈局變化。通過監聽全域性佈局的變化就可以得知軟鍵盤的狀態。
Android框架提供了一個ViewTreeObserver類,它是一個View檢視樹的觀察者類。ViewTreeObserver類中定義了一系列的公共介面(public interface)。當一個View attach到一個視窗上時就會建立一個ViewTreeObserver物件,這樣當一個View的檢視樹發生改變時,就會呼叫該物件的某個方法,將事件通知給每個註冊的監聽者。
OnGlobalLayoutListener是ViewTreeObserver中定義的眾多介面中的一個,它用來監聽一個檢視樹中全域性佈局的改變或者檢視樹中的某個檢視的可視狀態的改變。當軟鍵盤由隱藏變為顯示,或由顯示變為隱藏時,都會呼叫當前佈局中所有存在的View中的ViewTreeObserver物件的dispatchOnGlobalLayout()方法,此方法中會遍歷所有已註冊的OnGlobalLayoutListener,執行相應的回撥方法,將全域性佈局改變的訊息通知給每個註冊的監聽者。
向一個View中的ViewTreeObserver註冊OnGlobalLayoutListener的方法如下。

view.getViewTreeObserver().addOnGlobalLayoutListener(listener);

註冊OnGlobalLayoutListener時有一些需要注意的地方。

  1. 註冊的監聽在不使用時需要呼叫removeOnGlobalLayoutListener或removeGlobalOnLayoutListener來移除監聽,不然可能會導致記憶體洩露。通常可以在Activity的onCreate()方法中註冊監聽,在onDestory()方法中移除監聽。
  2. 並不是只有顯示和隱藏軟鍵盤會觸發OnGlobalLayoutListener中的回撥,一個View在繪製完成,或者消失時都會觸發OnGlobalLayoutListener中的回撥(由於在onCreate中無法獲取一個View的寬度和高度,很多時候就是通過註冊OnGlobalLayoutListener,在OnGlobalLayoutListener的回撥中來獲取一個View的寬度和高度)。

為了在OnGlobalLayoutListener的回撥中準確的判斷是否是由於軟鍵盤狀態改變引起的,以及獲取軟鍵盤的高度,還需要另外一個介面。

獲取當前視窗可見的顯示區域大小

在View中提供了一個方法getWindowVisibleDisplayFrame(),此方法會返回該view所附著的視窗的可見區域大小。當軟鍵盤顯示時,視窗的可見區域大小會被壓縮,當軟鍵盤隱藏時,視窗的可見區域大小會還原。不過並不是只有軟鍵盤的顯示和隱藏會影響視窗的可見區域大小,像大多數的平板和部分手機上有一排虛擬按鍵(虛擬的返回鍵,Home鍵等),虛擬按鍵的顯示和隱藏也會引起視窗可見區域的變化。不過好在除了軟鍵盤外,其他操作對視窗可見區域的影響佔整個螢幕大小的比例都不是很大,通過設定一個合理的閾值,就可以較準確的判斷出是否是軟鍵盤顯示和隱藏引起的佈局變化。
此外,getWindowVisibleDisplayFrame()會返回視窗的可見區域高度,通過和螢幕高度相減,就可以得到軟鍵盤的高度了。

監聽軟鍵盤的狀態變化

在獲取到軟鍵盤的狀態和高度後就可以執行需要的操作了。如重新佈局按鈕位置,設定變數,記錄當前軟鍵盤狀態和上次軟鍵盤隱藏時間等。不過如果有多個類需要根據軟鍵盤狀態來執行一些操作,如果每個類中都去這樣做一遍就很麻煩,而且也沒有必要。這時在可以自行定義一個介面,在主Activity中對軟鍵盤狀態變化進行監聽,其他對軟鍵盤狀態感興趣的類,向主Activity中註冊軟鍵盤狀態變化監聽。在主Activity中,當軟鍵盤狀態發生改變時通知監聽者。

完整示例程式碼

完整的示例程式碼如下。

public interface OnSoftKeyboardStateChangedListener {
    public void OnSoftKeyboardStateChanged(boolean isKeyBoardShow, int keyboardHeight);
}

//註冊軟鍵盤狀態變化監聽
public void addSoftKeyboardChangedListener(OnSoftKeyboardStateChangedListener listener) {
    if (listener != null) {
        mKeyboardStateListeners.add(listener);
    }
}
//取消軟鍵盤狀態變化監聽
public void removeSoftKeyboardChangedListener(OnSoftKeyboardStateChangedListener listener) {
    if (listener != null) {
        mKeyboardStateListeners.remove(listener);
    }
}

private ArrayList<OnSoftKeyboardStateChangedListener> mKeyboardStateListeners;      //軟鍵盤狀態監聽列表
private OnGlobalLayoutListener mLayoutChangeListener;
private boolean mIsSoftKeyboardShowing;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);
    mIsSoftKeyboardShowing = false;
    mKeyboardStateListeners = new ArrayList<OnSoftKeyboardStateChangedListener>();
    mLayoutChangeListener = new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            //判斷視窗可見區域大小
            Rect r = new Rect();
            getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
            //如果螢幕高度和Window可見區域高度差值大於整個螢幕高度的1/3,則表示軟鍵盤顯示中,否則軟鍵盤為隱藏狀態。
            int heightDifference = screenHeight - (r.bottom - r.top);
            boolean isKeyboardShowing = heightDifference > screenHeight/3;

            //如果之前軟鍵盤狀態為顯示,現在為關閉,或者之前為關閉,現在為顯示,則表示軟鍵盤的狀態發生了改變
            if ((mIsSoftKeyboardShowing && !isKeyboardShowing) || (!mIsSoftKeyboardShowing && isKeyboardShowing)) {
                mIsSoftKeyboardShowing = isKeyboardShowing;
                for (int i = 0; i < mKeyboardStateListeners.size(); i++) {
                    OnSoftKeyboardStateChangedListener listener = mKeyboardStateListeners.get(i);
                    listener.OnSoftKeyboardStateChanged(mIsSoftKeyboardShowing, heightDifference);
                }
            }
        }
    };
    //註冊佈局變化監聽
    getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(mLayoutChangeListener);
}

@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
@Override
protected void onDestroy() {
    //移除佈局變化監聽
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(mLayoutChangeListener);
    } else {
        getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(mLayoutChangeListener);
    }
    super.onDestroy();
};

其中screenHeight 是螢幕高度,關於螢幕高度的獲取方法,網上有很多,這裡就不介紹了。