Android 軟鍵盤踩坑記

最近在開發一個朋友圈產品的時候遇到一個bug:軟鍵盤遮罩,在解決的過程當中我經過百度、谷歌查找了好半天,最終經歷了一番坎坷才解決,具體過程且聽我娓娓道來!android

1、windowSoftInputMode

這個是在遇到軟鍵盤相關的問題,腦子裏第一個想到的知識點,可是效果如何呢?能解決問題,可是不完美! 先看沒有解決的效果圖:git

就是說當一個列表的最後一項須要輸入時,軟鍵盤會將EditText徹底遮罩! 其實將就一下這個效果也能夠,可是咱們能夠選擇不將就——死磕到底!所以咱們來看看 windowSoftInputMode 怎麼解決這個問題?github

Activity獲取焦點後,軟鍵盤隱藏與顯示
stateUnspecified 不指定軟鍵盤的狀態 (隱藏仍是可見) 將由系統選擇合適的狀態,或依賴主題中的設置,這是對軟鍵盤行爲的默認設置
stateUnchanged 保留狀態 當 Activity 轉至前臺時保留軟鍵盤最後所處的任何狀態,不管是可見仍是隱藏
stateHidden 隱藏軟鍵盤 當用戶確實是向前導航到 Activity,而不是因離開另外一Activity 而返回時隱藏軟鍵盤
stateAlwaysHidden 始終隱藏軟鍵盤 當 Activity 的主窗口有輸入焦點時始終隱藏軟鍵盤
stateVisible 顯示軟鍵盤 在正常的適宜狀況下(當用戶向前導航到 Activity 的主窗口時)顯示軟鍵盤
stateAlwaysVisible 始終顯示軟鍵盤 當用戶確實是向前導航到 Activity,而不是因離開另外一Activity 而返回時顯示軟鍵盤
在軟鍵盤彈出時,Activity調整策略
adjustUnspecified 主窗口的默認行爲,不指定 Activity 的主窗口是否調整尺寸覺得軟鍵盤騰出空間,或者窗口內容是否進行平移以在屏幕上顯露當前焦點。 系統會根據窗口的內容是否存在任何可滾動其內容的佈局視圖來自動選擇其中一種模式。 若是存在這樣的視圖,窗口將進行尺寸調整,前提是可經過滾動在較小區域內看到窗口的全部內容。
adjustResize 始終調整 Activity 主窗口的尺寸來爲屏幕上的軟鍵盤騰出空間。當軟鍵盤彈出時,會讓佈局從新繪製,這種通常適應於帶有滑動性質的控制,讓其向下滾動,而後適應軟鍵盤的顯示。
adjustPan 不調整 Activity 主窗口的尺寸來爲軟鍵盤騰出空間, 而是自動平移窗口的內容,使當前焦點永遠不被鍵盤遮蓋,讓用戶始終都能看到其輸入的內容。 這一般不如尺寸調整可取,由於用戶可能須要關閉軟鍵盤以到達被遮蓋的窗口部分或與這些部分進行交互。
adjustNoting 軟鍵盤彈出時,主窗口Activity不會作出任何響應。

上面的表格說明了兩個問題:軟鍵盤顯示與Activity響應策略。在上面的項目中,軟鍵盤顯示是沒有問題的,只是Activity的部份內容被遮罩,能夠調整策略解決的。那麼咱們來依次嘗試一下這些個響應策略!api

  • stateUnspecified:

默認的策略一進來軟鍵盤就將底部遮擋了,咱們都沒法操做底部的 item ,所以咱們須要進來時不顯示軟鍵盤,增長一個策略bash

android:windowSoftInputMode="stateHidden|stateUnspecified"
複製代碼

如今進來卻是不顯示了,可是點擊底部的item時仍是同樣會被遮擋:微信

  • adjustPan:
android:windowSoftInputMode="stateHidden|stateUnspecified"
複製代碼

adjustPan 策略確實將 Activity 主窗口平移上去了,可是個人 Title 部分也平移上去了!這就是我說的不完美的地方,那麼咱們試一下主窗口重繪呢?app

  • adjustResize :
android:windowSoftInputMode="stateHidden|adjustResize"
複製代碼

adjustResize 策略並無起到做用,底部的輸入界面依然被遮罩了,這裏我只能接受 adjustPan 策略了!可是還有一個 adjustNoting 策略看看會不會是同樣?既然死磕,我們就一個一個都試個遍!ide

  • adjustNoting
android:windowSoftInputMode="stateHidden|adjustNothing"
複製代碼

很好,果真沒有令咱們失望,確實是不行!佈局

而 ConstraintLayout、RelativeLayout 以及 FrameLayout 佈局將 EditText 置於佈局底部測試默認是正常的。post

可是爲何微信聊天頁面使用 RecyclerView 佈局的效果不是這樣的啊?爲此我聯繫到了一個仿朋友圈的大神,他告訴了我第二種方法:動態計算軟鍵盤的高度

2、動態計算軟鍵盤高度

動態計算軟鍵盤的高度這一塊,咱們增長一個難度,就是增長軟鍵盤與 EditText 之間的間距,提及來抽象,仍是上圖:

至於爲何要增長難度,還不是產品要求……既然人家能實現,咱也努把力實現唄!因爲動態計算軟鍵盤高度這件事,我們不須要再設置 SoftInputMode 了,由於整個過程純手工操做,不須要系統其它 api 支持了!

  1. 首先,咱們須要作一些準備工做,將軟鍵盤與主頁內容剝離,主頁內容就是一個 RecyclerView ,軟鍵盤部分是一個佈局包含的 EditText ,軟鍵盤佈局如圖:

  1. 將上面的軟鍵盤進行封裝,這個是重點。提及來比較抽象,就上一張流程圖和代碼:

public class EmojiPanelView extends LinearLayout implements OnKeyBoardStateListener {
	···
	 public EmojiPanelView(Context context) {
        super(context);
        init();
    }
     private void init() {
        View itemView = LayoutInflater.from(getContext()).inflate(R.layout.view_emoji_panel, this, false);
        mEditText = itemView.findViewById(R.id.edit_text);
        mEditText.setOnTouchListener((v, event) -> {
            showSoftKeyBoard();
            return true;
        });

        mImageSwitch = itemView.findViewById(R.id.img_switch);
        mImageSwitch.setOnClickListener(v -> {
            if (isKeyBoardShow) {
                mImageSwitch.setImageResource(R.drawable.input_keyboard_drawable);
                changeLayoutNullParams(false);
                hideSoftKeyBoard();
                changeEmojiPanelParams(mKeyBoardHeight);
            } else {
                mImageSwitch.setImageResource(R.drawable.input_smile_drawable);
                showSoftKeyBoard();
            }
        });
        ···
        addOnSoftKeyBoardVisibleListener((Activity) getContext(), this);
        addView(itemView);
    }
 @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getY() < Utils.getScreenHeight() - Utils.dp2px(254f) && isShowing()) {
            dismiss();
        }
        return super.onTouchEvent(event);
    }
private void showSoftKeyBoard() {
        InputMethodManager inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        if (inputMethodManager != null && mEditText != null) {
            mEditText.post(() -> {
                mEditText.requestFocus();
                inputMethodManager.showSoftInput(mEditText, 0);
            });
            new Handler().postDelayed(() -> {
                changeLayoutNullParams(true);
                changeEmojiPanelParams(0);
            }, 200);
        }
    }


    private void hideSoftKeyBoard() {
        InputMethodManager inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        if (inputMethodManager != null && mEditText != null) {
            inputMethodManager.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
        }
    }
 private void changeLayoutNullParams(boolean isShowSoftKeyBoard) {
        LinearLayout.LayoutParams params = (LayoutParams) mLayoutNull.getLayoutParams();
        if (isShowSoftKeyBoard) {
            params.weight = 1;
            params.height = 0;
            mLayoutNull.setLayoutParams(params);
        } else {
            params.weight = 0;
            params.height = mDisplayHeight;
            mLayoutNull.setLayoutParams(params);
        }
    }

    private void changeEmojiPanelParams(int keyboardHeight) {
        if (mLayoutEmojiPanel != null) {
            LinearLayout.LayoutParams params = (LayoutParams) mLayoutEmojiPanel.getLayoutParams();
            params.height = keyboardHeight;
            mLayoutEmojiPanel.setLayoutParams(params);
        }
    }

    boolean isVisiableForLast = false;

    public void addOnSoftKeyBoardVisibleListener(Activity activity, final OnKeyBoardStateListener listener) {
        final View decorView = activity.getWindow().getDecorView();
        decorView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
            Rect rect = new Rect();
            decorView.getWindowVisibleDisplayFrame(rect);
            //計算出可見屏幕的高度
            int displayHight = rect.bottom - rect.top;
            //得到屏幕總體的高度
            int hight = decorView.getHeight();
            //得到鍵盤高度
            int keyboardHeight = hight - displayHight - Utils.calcStatusBarHeight(getContext());
            boolean visible = (double) displayHight / hight < 0.8;
            if (visible != isVisiableForLast) {
                listener.onSoftKeyBoardState(visible, keyboardHeight, displayHight - Utils.dp2px(48f));
            }
            isVisiableForLast = visible;
        });
    }


    @Override
    public void onSoftKeyBoardState(boolean visible, int keyboardHeight, int displayHeight) {
        this.isKeyBoardShow = visible;
        if (visible) {
            mKeyBoardHeight = keyboardHeight;
            mDisplayHeight = displayHeight;
        }
    }

	}
複製代碼
  1. 將自定義的佈局加入到主頁內容當中,而後咱們不用設置 windowSoftInputMode 就能夠了。佈局:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.CustomActivity">

    <include
            android:id="@+id/custom_top_layout"
            layout="@layout/toolbar_layout"/>

    <android.support.v7.widget.RecyclerView
            android:id="@+id/custom_items"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintTop_toBottomOf="@+id/custom_top_layout"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
    >

    </android.support.v7.widget.RecyclerView>

    <com.sasucen.softinput.widget.EmojiPanelView
            android:id="@+id/layout_face_panel"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintBottom_toBottomOf="parent">

    </com.sasucen.softinput.widget.EmojiPanelView>

</android.support.constraint.ConstraintLayout>

複製代碼

效果圖:

這個效果仍是不錯的,可是如今大部分APP都是沉浸式狀態欄了,那麼咱們也加上沉浸式狀態欄看看!

哦豁,輸入框被遮罩了!接下來我們進入第三步——最終填坑!

最終填坑


我在走到這個地方的時候,當時記得抓瞎。百度了好多都沒有說起說起軟鍵盤遮罩和沉浸式狀態欄之間的聯繫,使用windowSoftInputMode 的時候有效果,可是並不理想,由於EditText與軟鍵盤沒有間距了,以下圖。

後來諮詢上面的大佬的時候,他給了我一個思路——狀態欄高度的丟失。後來我嘗試在屏幕可見高度以及屏幕總體高度的尺寸上作計算,結果都失敗了,EditText 徹底被遮罩!由於無論 layout 增長仍是仍是減小狀態欄的高度,EditText 的位置始終在軟鍵盤遮罩的位置。原本打算經過設置 titbar 的 padding 和修改狀態欄的顏色,實現沉浸式狀態欄,可是鑽牛角尖的我始終不甘心!後來想起以前看到的一篇文章隨手記技術團隊的博客介紹「fitsSystemWindows」屬性的說明,因而我進行了嘗試:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.CustomActivity">

   ······

    <com.sasucen.softinput.widget.EmojiPanelView
            android:id="@+id/layout_face_panel"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:layout_constraintBottom_toBottomOf="parent">

    </com.sasucen.softinput.widget.EmojiPanelView>

</android.support.constraint.ConstraintLayout>

複製代碼


以上所述便是我本身關於軟鍵盤的踩坑總結,但願本身在下次不清楚的時候能夠回來看看,也但願能夠幫助到有須要的人。若有謬誤,還請各位指正!

源碼

相關文章
相關標籤/搜索