Android鍵盤操做總結

Android 鍵盤相關常見問題有:java

  1. 限制輸入框內字數,超過字數不讓輸入,而且提示
  2. 點擊外部區域鍵盤自動收起
  3. 如何獲取鍵盤高度
  4. 鍵盤與面板的切換衝突

下面將對上述問題各個擊破。git

1. 限制輸入框內字數,超過字數不讓輸入,而且提示

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)});
複製代碼

2. 點擊外部區域鍵盤自動收起

若是當前頁面是Activity那麼能夠直接重寫dispatchTouchEvent方法。在ACTION_DOWN事件時,判斷點擊的座標是否在輸入框座標的上面,若是是那麼調用隱藏鍵盤的方法。github

若是當前頁面是Fragment,那麼Fragment中增長一個dispatchTouchEvent方法,內部邏輯同上,在Fragment所依賴的Activity代碼中將dispatchTouchEvent事件透傳給Fragment的dispatchTouchEvent,若是鍵盤須要隱藏,Fragment的dispatchTouchEvent方法須要返回true,表示消費本次全部觸摸事件,再也不繼續傳遞。api

3. 如何獲取鍵盤高度

首先須要知道一點,鍵盤高度不是固定的。用戶使用不一樣的輸入法,高度可能不同;甚至有些輸入法,能夠直接調節輸入法面板的高度。微信

3.1 有沒有系統的api能夠供咱們獲取鍵盤高度?

沒有。app

3.2 有什麼方法能夠間接獲取鍵盤高度?

系統給咱們提供了一個頁面佈局變化的監聽器OnGlobalLayoutListener,這個監聽器能夠通知咱們佈局發生改變,咱們能夠在此時獲取本身的高度,再經過屏幕寬度和狀態欄高度等間接計算出鍵盤的高度。ide

  • 那麼就有一個問題,OnGlobalLayoutListener接收到變化動做時,必定是鍵盤彈出或消失麼?工具

    不必定。佈局

  • 那爲何使用OnGlobalLayoutListener能夠監聽鍵盤的狀態?測試

    咱們知道每一個view的寬高變化都會致使OnGlobalLayoutListener的觸發,可是對當前Activity的Window對象中的DecorView進行監聽時,通常來講,DecorView的尺寸不會發生變化,發生變化的主要緣由就是鍵盤的收起和展開,這時候加上簡單的判斷(變化超過某個閾值)就能夠獲取鍵盤的高度,以及是否彈起。

  • 有沒有使用OnGlobalLayoutListener監聽鍵盤失效的情景?

    在Android7.0上,咱們可使用多任務鍵開啓分屏/多窗口模式,當咱們開啓分屏以後,調整分屏的分界線時,都會觸發DecorViewOnGlobalLayoutListener,可是此時鍵盤並未觸發任何動做;並且,當咱們點擊某個輸入框以後,鍵盤在分屏模式下會變成懸浮模式,不會擠壓Activity的控件,因此當鍵盤彈出或收起時,OnGlobalLayoutListener不會接收到任何事件。這就致使了OnGlobalLayoutListener徹底失效。還有一些其餘的場景緻使監聽鍵盤事件失效的情景,暫時想不起來,能夠在評論處補充。

3.3 獲取鍵盤高度

在通常狀況下,咱們對ActivityPhoneWindowDecorView的佈局變化進行監聽,通常來講,變化值超過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 鍵盤: 彈出 '''

4. 鍵盤與面板的切換衝突

4.1 問題描述

在IM聊天頁面,一般下面會作成相似於微信的樣式(點擊後可切換表情面板和鍵盤)。點擊表情按鈕,會彈出表情面板,且表情按鈕變成鍵盤模式;再次點擊鍵盤模式,或者點擊輸入框,會彈出輸入框,並收起表情面板。如下篇幅均稱表情面板爲面板。

https://user-gold-cdn.xitu.io/2018/5/30/163b083f80df3e74?w=454&h=426&f=png&s=70418

4.2 常規思路

一般這樣的頁面佈局是一個RecyclerView+輸入區域。輸入區域在RecyclerView下面,因此整個佈局可使用垂直的LinearLayout。鍵盤模式咱們選擇adjustResize

常規的邏輯以下

  • 輸入區域包含輸入框和下面的表情面板,默認表情面板的visibilityGONE
  • 點擊表情按鈕時,面板的可見性爲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);
            }
        }
    });
}
複製代碼

運行看看結果:

https://user-gold-cdn.xitu.io/2018/5/30/163b083f80b6aca9?w=376&h=673&f=gif&s=832475

出現了奇怪的一幀:

https://user-gold-cdn.xitu.io/2018/5/30/163b083f80f77972?w=372&h=667&f=png&s=89308

結論:

當屏幕中已顯示鍵盤時,點擊表情按鈕彈出面板前,須要隱藏鍵盤,可是隱藏鍵盤咱們只是調用的一個遠程服務(Context.INPUT_METHOD_SERVICE),它是什麼時候執行咱們沒法控制(通常來講,涉及跨進程通訊,因此執行順序確定是在面板顯示以後),因此咱們不管咱們先調用隱藏鍵盤api再顯示錶情面板,仍是先顯示錶情面板在調用隱藏鍵盤的api都會出現這一幀現象,給人的感受就是閃爍了一下。

4.3 解決方案

所以,咱們隱藏和顯示錶情面板的時機不是點擊表情按鈕時就馬上執行,而是須要等到輸入法面板徹底顯示或徹底隱藏後再進行。

這裏可能涉及到一些監聽鍵盤彈起/隱藏操做和獲取鍵盤高度的知識。能夠參見上一節小結如何獲取鍵盤高度。咱們獲取的鍵盤高度每次更新後直接存儲在SharedPreferences,某些應用須要從新將彈起的面板高度從新設置爲與鍵盤高度相同,如微信就須要記錄鍵盤的高度。但也不是每一個應用都須要面板與鍵盤高度一致,若是你的應用不須要能夠不用看如何獲取鍵盤高度。

若是咱們在OnGlobalLayoutListener中監聽鍵盤的彈出或收起,並根據相應狀態設置面板的隱藏或顯示時會出現一些閃爍的問題(代碼能夠看Demo中的半解決切換鍵盤衝突)。由於閃爍的時間很短,因此錄製gif的時候沒法看到,有興趣的能夠運行Demo中半解決切換鍵盤衝突方案。

以鍵盤彈起爲例,咱們的流程是這樣的:

觸發鍵盤彈起 --> OnGlobalLayoutListener接收到佈局變化 --> 此時鍵盤已經徹底彈起 --> X --> 隱藏表情面板

這個流程中的X指的是bug出現的時候,鍵盤徹底彈起時,表情鍵盤並無當即隱藏,而是隨後隱藏的,這就致使了半解決衝突的微弱閃爍的現象。

  • 爲何會出現這樣的微弱的閃爍?

咱們來看看ViewGroup的測量過程。ViewGroup測量時,會先去遍歷測量全部的子View的尺寸,而後結合ViewGroup的測量模式計算出合適的尺寸。在咱們這個案例裏,當表情面板已經展開時,若是切換到鍵盤,首頁鍵盤會擠壓整個佈局,也就是咱們說的ViewGroup的佈局,可是此時執行ViewGroup的onMeasure時,裏面的表情面板仍然是可見的。而後咱們在OnGlobalLayoutListener的回調裏將表情面板的可見性設爲GONE, 但此時已經和鍵盤剛展開時已經不是同一幀了,因此看到了微弱的閃爍效果。

根據上面的分析,咱們須要在鍵盤收起時的那一幀中,測量ViewGroup尺寸時,直接從新測量的面板控件的尺寸就能夠了。咱們把表情區域放進一個自定義的佈局控件KBPanelConflictLayout,整個頁面的根佈局設爲自定義控件KBRootConflictLayout(代碼能夠看Demo中的解決切換鍵盤衝突)。

KBRootConflictLayoutonMeasure方法中,根據佈局高度變化是否超過某個閾值來判斷是否鍵盤彈起。

@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();
            }
        }
    }
}
複製代碼

KBPanelConflictLayoutonMeasure方法中,咱們根據是否隱藏狀態來判斷是否須要把鍵盤的高度變爲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);
}
複製代碼

這樣在測試下,看看沒有閃爍衝突的效果圖吧。

https://user-gold-cdn.xitu.io/2018/5/30/163b083f80ce1038?w=376&h=673&f=gif&s=516860

若是你把上述代碼整理優化下,加上對RelativeLayout和FrameLayout的支持,對設置是否調整面板高度與鍵盤一致的支持,對多面板的切換的支持,提供一些工具類給用戶直接調用,那就是2000+star的github.com/Jacksgong/J…項目了。

附上本文Demo地址,歡迎點心:

github.com/hust2010107…

相關文章
相關標籤/搜索