Android 鍵盤相關常見問題有:java
下面將對上述問題各個擊破。git
etReply.setFilters(new InputFilter[]{new InputFilter() {
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
if (source.length() + dest.length() > COMMENT_MAX_NUM) {
Crouton.makeText(AppUtils.getString(R.string.infodetail_comment_limit), Style.ALERT).show();
}
return null;
}
}, new InputFilter.LengthFilter(COMMENT_MAX_NUM)});
複製代碼
若是當前頁面是Activity那麼能夠直接重寫dispatchTouchEvent
方法。在ACTION_DOWN
事件時,判斷點擊的座標是否在輸入框座標的上面,若是是那麼調用隱藏鍵盤的方法。github
若是當前頁面是Fragment,那麼Fragment中增長一個dispatchTouchEvent
方法,內部邏輯同上,在Fragment所依賴的Activity代碼中將dispatchTouchEvent
事件透傳給Fragment的dispatchTouchEvent
,若是鍵盤須要隱藏,Fragment的dispatchTouchEvent
方法須要返回true
,表示消費本次全部觸摸事件,再也不繼續傳遞。api
首先須要知道一點,鍵盤高度不是固定的。用戶使用不一樣的輸入法,高度可能不同;甚至有些輸入法,能夠直接調節輸入法面板的高度。微信
沒有。app
系統給咱們提供了一個頁面佈局變化的監聽器OnGlobalLayoutListener
,這個監聽器能夠通知咱們佈局發生改變,咱們能夠在此時獲取本身的高度,再經過屏幕寬度和狀態欄高度等間接計算出鍵盤的高度。ide
那麼就有一個問題,OnGlobalLayoutListener
接收到變化動做時,必定是鍵盤彈出或消失麼?工具
不必定。佈局
那爲何使用OnGlobalLayoutListener
能夠監聽鍵盤的狀態?測試
咱們知道每一個view的寬高變化都會致使
OnGlobalLayoutListener
的觸發,可是對當前Activity的Window對象中的DecorView進行監聽時,通常來講,DecorView的尺寸不會發生變化,發生變化的主要緣由就是鍵盤的收起和展開,這時候加上簡單的判斷(變化超過某個閾值)就能夠獲取鍵盤的高度,以及是否彈起。
有沒有使用OnGlobalLayoutListener
監聽鍵盤失效的情景?
在Android7.0上,咱們可使用多任務鍵開啓分屏/多窗口模式,當咱們開啓分屏以後,調整分屏的分界線時,都會觸發
DecorView
的OnGlobalLayoutListener
,可是此時鍵盤並未觸發任何動做;並且,當咱們點擊某個輸入框以後,鍵盤在分屏模式下會變成懸浮模式,不會擠壓Activity的控件,因此當鍵盤彈出或收起時,OnGlobalLayoutListener
不會接收到任何事件。這就致使了OnGlobalLayoutListener
徹底失效。還有一些其餘的場景緻使監聽鍵盤事件失效的情景,暫時想不起來,能夠在評論處補充。
在通常狀況下,咱們對Activity
的PhoneWindow
中DecorView
的佈局變化進行監聽,通常來講,變化值超過60dp
就能夠認爲是鍵盤彈出或收起了。並且在非全屏主題下, 鍵盤高度 = 屏幕高度 - 狀態欄高度 - 主視圖高度 - 標題欄高度, 因而咱們能夠經過下面代碼間接計算出鍵盤高度。
mDecorView = this.getActivity().getWindow().getDecorView();
mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect rect = new Rect();
mDecorView.getWindowVisibleDisplayFrame(rect);
// 不能使用decorView.getHeight()獲取decorview的高度,獲取的高度不會發生變化
int displayHeight = rect.bottom;
if (Math.abs(displayHeight - mOldDecorViewHeight) > dp60) {
mOldDecorViewHeight =displayHeight;
int rootHeight = mRoot.getHeight();
int statusBarHeight = getStatusBarHeight();
int screenHeight = getScreenHeight();
int titleBarHeight = getTitleBarHeight();
//在非全屏模式下, 鍵盤高度 = 屏幕高度 - 狀態欄高度 - 主視圖高度 - 標題欄高度
int keyboardHeight = screenHeight - statusBarHeight - rootHeight - titleBarHeight;
Log.i("lxc", "keyboardHeight ---> " + keyboardHeight + " 鍵盤: " + (keyboardHeight > 0 ? "彈出" : "收起"));
}
}
};
mDecorView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
複製代碼
當我點擊輸入框彈出和收起鍵盤時,會出現下面的log日誌:
'''console 05-29 15:22:50.368 8982-8982/com.orzangleli.myapplication I/lxc: keyboardHeight ---> 0 鍵盤: 收起 05-29 15:22:51.736 8982-8982/com.orzangleli.myapplication I/lxc: keyboardHeight ---> 873 鍵盤: 彈出 05-29 15:22:52.739 8982-8982/com.orzangleli.myapplication I/lxc: keyboardHeight ---> 0 鍵盤: 收起 05-29 15:22:53.892 8982-8982/com.orzangleli.myapplication I/lxc: keyboardHeight ---> 873 鍵盤: 彈出 '''
在IM聊天頁面,一般下面會作成相似於微信的樣式(點擊後可切換表情面板和鍵盤)。點擊表情按鈕,會彈出表情面板,且表情按鈕變成鍵盤模式;再次點擊鍵盤模式,或者點擊輸入框,會彈出輸入框,並收起表情面板。如下篇幅均稱表情面板爲面板。
一般這樣的頁面佈局是一個RecyclerView+輸入區域。輸入區域在RecyclerView下面,因此整個佈局可使用垂直的LinearLayout。鍵盤模式咱們選擇adjustResize
。
常規的邏輯以下:
visibility
爲GONE
。VISIBLE
;收起輸入法鍵盤;按鈕圖片變爲鍵盤模式。GONE
;展開輸入法鍵盤;按鈕推盤變成表情模式。咱們按照上述思路寫下關鍵代碼:
private void initView(View root) {
mInputEt = root.findViewById(R.id.et_input);
mFaceBtn = root.findViewById(R.id.btn_face);
mPanel = root.findViewById(R.id.panel);
mFaceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPanel.getVisibility() == View.VISIBLE) {
mPanel.setVisibility(View.GONE);
mFaceBtn.setImageResource(R.drawable.emoji_download_icon);
openKeyBoard(mInputEt);
} else {
mPanel.setVisibility(View.VISIBLE);
mFaceBtn.setImageResource(R.drawable.zz_chat_reply_keyboard);
closeKeyBoard(mInputEt);
}
}
});
}
複製代碼
運行看看結果:
出現了奇怪的一幀:
結論:
當屏幕中已顯示鍵盤時,點擊表情按鈕彈出面板前,須要隱藏鍵盤,可是隱藏鍵盤咱們只是調用的一個遠程服務(Context.INPUT_METHOD_SERVICE
),它是什麼時候執行咱們沒法控制(通常來講,涉及跨進程通訊,因此執行順序確定是在面板顯示以後),因此咱們不管咱們先調用隱藏鍵盤api再顯示錶情面板,仍是先顯示錶情面板在調用隱藏鍵盤的api都會出現這一幀現象,給人的感受就是閃爍了一下。
所以,咱們隱藏和顯示錶情面板的時機不是點擊表情按鈕時就馬上執行,而是須要等到輸入法面板徹底顯示或徹底隱藏後再進行。
這裏可能涉及到一些監聽鍵盤彈起/隱藏操做和獲取鍵盤高度的知識。能夠參見上一節小結如何獲取鍵盤高度。咱們獲取的鍵盤高度每次更新後直接存儲在SharedPreferences
,某些應用須要從新將彈起的面板高度從新設置爲與鍵盤高度相同,如微信就須要記錄鍵盤的高度。但也不是每一個應用都須要面板與鍵盤高度一致,若是你的應用不須要能夠不用看如何獲取鍵盤高度。
若是咱們在OnGlobalLayoutListener中監聽鍵盤的彈出或收起,並根據相應狀態設置面板的隱藏或顯示時會出現一些閃爍的問題(代碼能夠看Demo中的半解決切換鍵盤衝突)。由於閃爍的時間很短,因此錄製gif的時候沒法看到,有興趣的能夠運行Demo中半解決切換鍵盤衝突方案。
以鍵盤彈起爲例,咱們的流程是這樣的:
觸發鍵盤彈起 --> OnGlobalLayoutListener接收到佈局變化 --> 此時鍵盤已經徹底彈起 --> X --> 隱藏表情面板
這個流程中的X
指的是bug出現的時候,鍵盤徹底彈起時,表情鍵盤並無當即隱藏,而是隨後隱藏的,這就致使了半解決衝突的微弱閃爍的現象。
咱們來看看ViewGroup的測量過程。ViewGroup測量時,會先去遍歷測量全部的子View的尺寸,而後結合ViewGroup的測量模式計算出合適的尺寸。在咱們這個案例裏,當表情面板已經展開時,若是切換到鍵盤,首頁鍵盤會擠壓整個佈局,也就是咱們說的ViewGroup的佈局,可是此時執行ViewGroup的onMeasure時,裏面的表情面板仍然是可見的。而後咱們在
OnGlobalLayoutListener
的回調裏將表情面板的可見性設爲GONE
, 但此時已經和鍵盤剛展開時已經不是同一幀了,因此看到了微弱的閃爍效果。
根據上面的分析,咱們須要在鍵盤收起時的那一幀中,測量ViewGroup尺寸時,直接從新測量的面板控件的尺寸就能夠了。咱們把表情區域放進一個自定義的佈局控件KBPanelConflictLayout
,整個頁面的根佈局設爲自定義控件KBRootConflictLayout
(代碼能夠看Demo中的解決切換鍵盤衝突)。
在KBRootConflictLayout
的onMeasure
方法中,根據佈局高度變化是否超過某個閾值來判斷是否鍵盤彈起。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
preNotifyChild(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void preNotifyChild(int width, int height) {
if (mOldHeight < 0) {
mOldHeight = height;
return ;
}
int deltaY = height - mOldHeight;
mOldHeight = height;
int minKeyboardHeight = 180;
if (Math.abs(deltaY) >= minKeyboardHeight) {
if (deltaY < 0) {
// 鍵盤彈起
if (mKBPanelConflictLayout != null) {
// 隱藏面板
mKBPanelConflictLayout.setHide();
}
} else {
// 鍵盤收起
if (mKBPanelConflictLayout != null) {
// 顯示面板
mKBPanelConflictLayout.setShow();
}
}
}
}
複製代碼
在KBPanelConflictLayout
的onMeasure
方法中,咱們根據是否隱藏狀態來判斷是否須要把鍵盤的高度變爲0.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mHide) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void setHide() {
this.mHide = true;
setVisibility(View.GONE);
}
public void setShow() {
this.mHide = false;
setVisibility(View.VISIBLE);
}
複製代碼
這樣在測試下,看看沒有閃爍衝突的效果圖吧。
若是你把上述代碼整理優化下,加上對RelativeLayout和FrameLayout的支持,對設置是否調整面板高度與鍵盤一致的支持,對多面板的切換的支持,提供一些工具類給用戶直接調用,那就是2000+star的github.com/Jacksgong/J…項目了。
附上本文Demo地址,歡迎點心: