Android 軟鍵盤相關問題

1. windowSoftInputMode屬性的使用

Android使用windowSoftInputMode來控制Activity 的主窗口與包含屏幕軟鍵盤的窗口的交互方式。 該屬性的設置影響兩個方面:php

  • 當 Activity 成爲用戶注意的焦點時軟鍵盤的狀態 — 隱藏仍是可見。
  • 對 Activity 主窗口所作的調整 — 是否將其尺寸調小覺得軟鍵盤騰出空間,或者當窗口部分被軟鍵盤遮擋時是否平移其內容以使當前焦點可見。

該設置必須是下表所列的值之一,或者是一個「state...」值加上一個「adjust...」值的組合。 在任一組中設置多個值(例如,多個「state...」值)都會產生未定義結果。各值之間使用垂直條 (|) 分隔。 例如:java

<activity android:windowSoftInputMode="stateVisible|adjustResize" . . .複製代碼

此處設置的值(「stateUnspecified」和「adjustUnspecified」除外)替換主題中設置的值。android

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

系統默認值爲:stateUnspecified|adjustUnspecifiedapp

上述是引用android官方文檔的說明,可是這個並不能讓咱們理解全部內容。所以本次將具體探究這9個屬性是如何影響。ide

1. stateUnspecified

android官方描述爲:不指定軟鍵盤的狀態(隱藏仍是可見)。 將由系統選擇合適的狀態,或依賴主題中的設置。這是對軟鍵盤行爲的默認設置。哪系統認爲的合適的狀態是什麼樣的呢?佈局

咱們採用以下佈局測試

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

    <EditText android:layout_width="match_parent" android:layout_height="wrap_content" />
</LinearLayout>複製代碼

咱們發現軟鍵盤沒有自動彈出,須要手動點擊EditText後,鍵盤纔會彈出來。
若是採用以下佈局spa

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
    <ScrollView android:layout_width="match_parent" android:layout_height="match_parent">
        <EditText android:layout_width="match_parent" android:layout_height="wrap_content" />
    </ScrollView>
</LinearLayout>複製代碼

咱們發現軟鍵盤會自動彈出。這個就很奇怪了,爲何就加了一個ScrollView,難道是由於添加了ScrollView,軟鍵盤就能夠自動彈出來嗎?咱們看一下以下的佈局3d

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
    <ScrollView android:layout_width="match_parent" android:layout_height="match_parent">
        <Button android:layout_width="match_parent" android:layout_height="wrap_content" />
    </ScrollView>
</LinearLayout>複製代碼

運行代碼能夠發現,這樣軟鍵盤也會不會自動彈出來。說明軟鍵盤自動彈出和ScrollView沒有直接關係。code

若是咱們採用以下佈局,咱們發現軟鍵盤仍是會自動彈出

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

    <ScrollView android:layout_width="match_parent" android:layout_height="match_parent">

        <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">

            <EditText android:layout_width="match_parent" android:layout_height="wrap_content" />

            <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="測試" android:id="@+id/btn_test"/>
        </LinearLayout>
    </ScrollView>
</LinearLayout>複製代碼

如何依舊採用上述的佈局,可是咱們在onCreate中加上以下代碼

Button button= (Button) findViewById(R.id.btn_test);
    button.setFocusable(true);
    button.setFocusableInTouchMode(true);
    button.requestFocus();
    button.requestFocusFromTouch();複製代碼

咱們發現軟鍵盤將不會自動彈出來。

如今咱們總結一下,當屬性設置爲stateUnspecified時,系統默認時不會自動彈出軟鍵盤,可是當界面上有滾動需求時(有ListView或ScrollView等)同時有得到焦點的輸入框軟鍵盤將自動彈出來(若是得到焦點不是輸入框也不會自動彈出軟鍵盤)。

2. stateUnspecified

這個比較簡單,進入當前界面是否彈出軟鍵盤由上一個界面決定。若是離開上一個界面,鍵盤是打開,那邊該界面鍵盤就是打開;不然就是關閉的.

3. stateHidden

這個也比較簡單,進入當前界面無論當前上一個界面或者當前界面如何,默認不顯示軟鍵盤。

4. stateAlwaysHidden

這個參數和stateHidden的區別是當咱們跳轉到下個界面,若是下個頁面的軟鍵盤是顯示的,而咱們再次回來的時候,軟鍵盤就會隱藏起來。而設置爲stateHidden,咱們再次回來的時候,軟鍵盤講顯示出來。

5. stateVisible

設置這個屬性,進入這個界面時不管界面是否須要彈出軟鍵盤,軟鍵盤都會顯示。

6. stateAlwaysVisible

這個參數和stateAlwaysVisible的區別是當咱們從這個界面跳轉到下個界面,從下個界面返回到這個界面是軟鍵盤是消失的,當回到這個界面軟鍵盤依舊能夠彈出顯示,而stateVisible確不會。

上述6個屬性定義的是進入界面,軟鍵盤是否顯示的。下面3個屬性設置的是軟鍵盤和界面顯示內容之間的顯示關係。

7. adjustResize,adjustPan

咱們將這兩個屬性放在一塊兒討論。爲了說明這個問題,咱們先看以下的佈局例子

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

    <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="3" android:background="@android:color/holo_red_dark">

        <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="這個是一個測試" android:textSize="30sp" />
    </FrameLayout>

    <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="4" android:background="@android:color/holo_blue_light">

        <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="這個是一個測試" android:textSize="30sp" />
    </FrameLayout>
    <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:background="@android:color/white">

        <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" />
    </FrameLayout>
</LinearLayout>複製代碼

adjustResize

adjustResize
adjustResize

adjustPan

adjustPan
adjustPan

由上面兩張圖咱們能夠知道,若是設置adjustResize屬性,當鍵盤顯示時,經過對界面主窗口的大小調整(壓縮等)來實現留出軟鍵盤的大小空間;若是設置adjustPan屬性,當鍵盤顯示時,經過對界面佈局的移動來保證輸入框能夠顯示出來。

8. adjustUnspecified

這個屬性是根據界面中否有能夠滾動的控件來判斷界面是採用adjustResize仍是adjustPan來顯示軟鍵盤。若是有能夠滾動的控制,那能夠將其理解爲adjustResize,經過壓縮界面來實現.可是是有一個前提的:可經過滾動在較小區域內看到窗口的全部內容。若是沒有能夠滾動的控件或者不符合前提條件,則是採用adjustPan,及移動佈局來實現。

2. 軟鍵盤的打開和關閉

軟鍵盤的打開和關閉主要經過InputMethodManager實現。InputMethodManager關於打開關閉軟鍵盤的方法主要有以下幾個方法。

方法 說明
hideSoftInputFromInputMethod(IBinder token, int flags) 關閉/隱藏軟鍵盤,讓用戶看不到軟鍵盤,可是傳入的token必須是是系統中token。
hideSoftInputFromWindow(IBinder windowToken, int flags) 和下面hideSoftInputFromWindow方法同樣,只是resultReceiver傳入的值null
hideSoftInputFromWindow(IBinder windowToken, int flags, ResultReceiver resultReceiver) 關閉/隱藏軟鍵盤,和第一個方法的區別是他傳入的token是系統界面中View窗口的token
showSoftInput(View view, int flags, ResultReceiver resultReceiver) 和hideSoftInputFromWindow對應
showSoftInput(View view, int flags) 同上面一個方法,可是默認的resultReceiver爲null
showSoftInputFromInputMethod(IBinder token, int flags) 和hideSoftInputFromInputMethod對應
toggleSoftInput(int showFlags, int hideFlags) 該方法是切換軟鍵盤,若是軟鍵盤打開,調用該方法軟鍵盤關閉;反之若是軟鍵盤關閉,那麼就打開
toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) 和上面一個方法同樣,區別是傳入的當前View的token
  1. 在上述方法中咱們看到須要傳入相關flags,相關flags有以下幾個

    參數 | 說明
    -------------------|---
    HIDE_IMPLICIT_ONLY | 表示軟鍵盤只有在用戶未明確顯示的狀況才被隱藏
    HIDE_NOT_ALWAYS | 表示軟鍵盤將一致隱藏,除非調用SHOW_FORCED纔會顯示
    SHOW_FORCED | 表示軟鍵盤強制顯示不會被關閉,除非用戶明確的要關閉
    SHOW_IMPLICIT | 表示軟鍵盤接收到一個不明確的顯示請求。

  2. 咱們常常看到用戶傳入的參數爲0,不屬於上述4個,那顯示的是什麼呢?其實若是傳入的hideflag的話表示的是就是關閉軟鍵盤,以前傳入的參數不會改變,showflag的話表示的是SHOW_IMPLICIT

總結:

關閉軟鍵盤的方法爲

public void hideKeyboard(View view) {
        ((InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(
                view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
    }複製代碼

打開關鍵盤的方法爲

public void OpenKeyboard(View view) {
        ((InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_NOT_ALWAYS);
    }複製代碼

3. 監聽軟鍵盤的打開和關閉

3.1 常規方法

監聽軟鍵盤的打開和關閉最常規的方法是監聽View的層次結構,這個View的層次結構的發生全局性的事件如佈局發生變化等咱們能夠經過調用getViewTreeObserver監聽到佈局的變化。代碼實例以下:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
        public void onGlobalLayout() {
        // 虛擬鍵的高度
        int navigationBarHeight = 0;

        int resourceId = activityRootView.getContext().getResources().getIdentifier("navigation_bar_height", "dimen", "android");
        //判斷是否有虛擬鍵
        if (resourceId > 0 && checkDeviceHasNavigationBar(activityRootView.getContext())) {
            navigationBarHeight = activityRootView.getResources().getDimensionPixelSize(resourceId);
        }

        // status bar的高度
        int statusBarHeight = 0;
        resourceId = activityRootView.getContext().getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            statusBarHeight = activityRootView.getResources().getDimensionPixelSize(resourceId);
        }

        // 得到app的顯示大小信息
        Rect rect = new Rect();
        ((Activity)activityRootView.getContext()).getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);

        //得到軟鍵盤的大小:屏幕高度-(status bar的高度+虛擬鍵的高度+app的顯示高度)
        int keyboardHeight = ((Activity) activityRootView.getContext()).getWindow().getDecorView().getRootView().getHeight() - (statusBarHeight + navigationBarHeight + rect.height());

        if (keyboardHeight>100 && !isSoftKeyboardOpened) {
            isSoftKeyboardOpened = true;
            notifyOnSoftKeyboardOpened(keyboardHeight);
            Log.d(TAG, "keyboard has been opened");

        } else  if(keyboardHeight <100 && isSoftKeyboardOpened){
            isSoftKeyboardOpened = false;
            notifyOnSoftKeyboardClosed();
            Log.d(TAG, "keyboard has been closed");
        }
        }
    } )複製代碼

status bar是否存在其實也須要判斷,可是由於app自己能夠判斷當前界面是否顯示status的高度,因此上述代碼默認status顯示。

3.2 重寫根佈局的onMeasure

該方法使用於windowSoftInputMode被設置爲adjustResize。如上文所致,adjustResize是採用壓縮界面佈局來實現軟鍵盤能夠正常顯示。具體代碼以下

public class KeyBoardFrameLayout extends FrameLayout {

    public KeyBoardFrameLayout(@NonNull Context context) {
        super(context);
    }

    public KeyBoardFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public KeyBoardFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();

        int keyboardHeight = actualHeight - proposedheight;
        if (keyboardHeight > 100) {
           notifyOnSoftKeyboardOpened(keyboardHeight);
        } else if (keyboardHeight < -100) {
            notifyOnSoftKeyboardClosed();
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }複製代碼

對比上述兩種方法:

  1. 第一種方法的優勢是無論windowSoftInputMode被設置爲何,均可以實現軟鍵盤的開關鍵盤;第二種須要將上述的佈局做爲界面的根佈局才能實現監聽
  2. 第一種方法的缺點是過後監聽,已當onLayout以後纔會拿到監聽,這個致使界面容易出現閃屏的狀況;而第二種方法是在界面onLayout以前就拿到了監聽,所以不會出現閃屏的狀況。
相關文章
相關標籤/搜索