1. 程式人生 > >Android: 實現類似QQ、微信的表情輸入鍵盤

Android: 實現類似QQ、微信的表情輸入鍵盤

需求

最近在寫北郵人論壇客戶端時,有一個需求是實現像手機QQ、微信那樣的表情輸入鍵盤,效果圖:

demo

表情鍵盤本身並不難做,無非就是一個帶SlidingTab的ViewPager,困擾我的地方在於,如何正確處理系統軟鍵盤與表情鍵盤之間的顯隱關係。

Google了一下,大概有這麼幾種思路:

第一種:動態改變SoftInputMode

這篇博文是國內網上轉載比較多的方法,軟鍵盤顯示時將SoftInputMode設定為「stateVisible|adjustResize」,表情鍵盤顯示時調整為「adjustPan」。

但在我實際使用過程中效果並不理想,一是我需要在一個ListView的底部實現表情鍵盤,這樣動態更改SoftInputMode會導致ListView上下跳動;二是切換到別的介面再切換回來時軟鍵盤的顯隱狀態偶爾會有衝突,最終我放棄了這種方法。

第二種:Dialog

Emoticons-Keyboard,這個專案的實現方法是直接在軟鍵盤上覆蓋顯示一個Dialog,避開了大部分的顯示邏輯操作,思路非常獨特,可惜我編譯執行後發現顯示效果並不好,除了動畫效果,最大的問題仍然是是從別的介面切換過來時,與軟鍵盤的顯示有衝突

基本思路

上面提到的兩個專案給了我很大的啟發,我反覆嘗試了微信、微博、手機QQ等應用的表情鍵盤邏輯,發現它們切換鍵盤並不會導致ListView跳動,如果沒有別的什麼黑科技的話,基本可以斷定使用的SoftInputMode就是adjustPan。(SoftInputMode各個屬性值的意義

既然是adjustPan就好說了,軟鍵盤顯示的時候不會導致ListView跳動,那麼Activity的底部必然有一個跟軟鍵盤相同高度的View被軟鍵盤覆蓋了,這個View其實就是表情輸入鍵盤,這樣點選表情按鈕的時候只需要顯示隱藏軟鍵盤,背後的表情框就顯示出來了。

思路有了,接下來就是梳理一下所需要的技術點:

  • 如何檢測軟鍵盤高度(用於動態設定表情鍵盤的高度)?
  • 在程式碼中如何手動顯示/隱藏軟鍵盤?
  • 如何防止從別的介面切換過來時,軟鍵盤狀態改變了有可能導致的顯示衝突?

如果這三個問題解決了,需求就基本實現了。

檢測軟鍵盤的高度

直接上程式碼:

private int getSupportSoftInputHeight() {
    Rect r = new Rect();
    mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(r);
    int screenHeight = mContext.getWindow().getDecorView().getRootView().getHeight();
    int
softInputHeight = screenHeight - r.bottom; if (Build.VERSION.SDK_INT >=
20) { // When SDK Level >= 20 (Android L), the softInputHeight will contain the height of softButtonsBar (if has) softInputHeight = softInputHeight - getSoftButtonsBarHeight(); } return softInputHeight; }

這裡的原理是通過當前的Activity拿到其RootView的高度,減去Activity本身實際的高度,就等於軟鍵盤的高度了。但在實際應用過程中發現,某些Android版本下,沒有顯示軟鍵盤時減出來的高度總是144,而不是零,經過反覆研究,最後發現這個高度是包括了虛擬按鍵欄的,所以在API Level高於18時,我們需要減去底部虛擬按鍵欄的高度(如果有的話)。

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private int getSoftButtonsBarHeight() {
    DisplayMetrics metrics = new DisplayMetrics();
    mContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
    int usableHeight = metrics.heightPixels;
    mContext.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
    int realHeight = metrics.heightPixels;
    if (realHeight > usableHeight) {
        return realHeight - usableHeight;
    } else {
        return 0;
    }
}

將高度設定給表情鍵盤就比較簡單了:

LinearLayout.LayoutParams linearParams = (LinearLayout.LayoutParams) mEmotionLayout.getLayoutParams();
linearParams.height = getSupportSoftInputHeight();

在程式碼中手動顯示、隱藏軟鍵盤

也是直接上程式碼了,這兩個方法也比較容易查到:

private void showSoftInput() {
    mInputManager.showSoftInput(mEditText, 0);
}

private void hideSoftInput() {
    mInputManager.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
}

解決切換程式時的顯示衝突

在預設狀態(StateUnspecified)下,在程式內開啟軟鍵盤然後點選Home鍵或多工鍵切換出去時,軟鍵盤會收起。再次進入程式介面也不會開啟,前文提到的兩個專案就是在這種情況下會出現問題。如何保證軟鍵盤和表情鍵盤的同步,直觀反應就是監聽軟鍵盤的高度變化,查了一下,果然可以監聽:

mEditText.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int softInputHeight = getSupportSoftInputHeight();
        if (softInputHeight != lastSoftInputHeight) {
            // do Something
        }
    }
});

實際測試中,這個函式在執行時會呼叫很多次,我們只需要在高度變化時做處理即可。


如上圖,一共有三種狀態,表情鍵盤的狀態分別為:gone、invisible和visible。分別判斷這三個狀態之間的轉化關係,然後動態的設定Visiblity即可:

public void onGlobalLayout() {
    int softInputHeight = getSupportSoftInputHeight();
    if (softInputHeight != lastSoftInputHeight) {
        if (softInputHeight <= 0) {
            lastSoftInputHeight = softInputHeight;
            if (!notHideEmojiLayout) {
                mEmotionLayout.setVisibility(View.GONE);
            } else {
                notHideEmojiLayout = false;
            }
        } else {
            lastSoftInputHeight = softInputHeight;
            LinearLayout.LayoutParams linearParams = (LinearLayout.LayoutParams) mEmotionLayout.getLayoutParams();
            linearParams.height = softInputHeight;
            mEmotionLayout.setVisibility(View.INVISIBLE);
            if (linearParams.height == softInputHeight) {
                mEmotionLayout.setVisibility(View.INVISIBLE);
            } else {
                linearParams.height = softInputHeight;
            }

            sp.edit().putInt(SHARE_PREFERENCE_TAG, softInputHeight).apply();
        }
    }
}

一點小Bug

由於Android裝置的多樣性,軟鍵盤高度不一致,所以需要動態的設定表情鍵盤的高度,然而程式在第一次軟鍵盤彈出後才能檢測到軟鍵盤高度,但這時由於表情鍵盤高度與軟鍵盤不一致,會導致顯示有點異常。所以程式會將檢測到的高度儲存到SharedPreference中,在Activity載入時讀出高度即可。

不過即使是這樣,在整個程式第一次進入這個介面時還是會顯示異常,暫時的解決辦法是在其他軟鍵盤彈出的頁面檢測一次軟鍵盤高度

如果你有更好的辦法,請留言交流~

完整程式碼

本文的完整的程式碼在我的Github上:Android-EmotionInputDetector,支援Gradle呼叫,喜歡的話不妨給個Star~