Android: 實現類似QQ、微信的表情輸入鍵盤
需求
最近在寫北郵人論壇客戶端時,有一個需求是實現像手機QQ、微信那樣的表情輸入鍵盤,效果圖:
表情鍵盤本身並不難做,無非就是一個帶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~