1. 程式人生 > >android全屏/沉浸式狀態列下,各種鍵盤擋住輸入框解決辦法

android全屏/沉浸式狀態列下,各種鍵盤擋住輸入框解決辦法

*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出

在開發中,經常會遇到鍵盤擋住輸入框的情況,比如登入介面或註冊介面,彈出的軟鍵盤把登入或註冊按鈕擋住了,使用者必須把軟鍵盤收起,才能點選相應按鈕,這樣的使用者體驗非常不好。像微信則直接把登入按鈕做在輸入框的上面,但有很多情況下,這經常滿足不了需求。同時如果輸入框特別多的情況下,點選輸入時,當前輸入框沒被擋住,但是當前輸入框下面的輸入框卻無法獲取焦點,必須先把鍵盤收起,再去獲取下面輸入框焦點,這樣使用者體驗也非常不好,那有什麼辦法呢?
系統的adjustResize和adjustPan有什麼區別,他們使用時的注意事項,有什麼系統要求及蔽端呢?

下面對幾種在開發中常用的方法進行總結:

方法一:非透明狀態列下使用adjustResize和adjustPan,或是透明狀態列下使用fitsSystemWindows=true屬性

主要實現方法:
在AndroidManifest.xml對應的Activity裡新增
android:windowSoftInputMode=”adjustPan”或是android:windowSoftInputMode=”adjustResize”屬性
這兩種屬性的區別,官方的解釋是:
這裡寫圖片描述

這兩個屬性作用都是為了調整介面使鍵盤不擋住輸入框 ,我這裡對這兩種屬性使用場景、優缺點、注意事項進行了全方面總結,不知大家平時使用時是否注意到了。

屬性 注意事項 優缺點 失效情況 適用情況
adjustResize 需要介面本身可調整尺寸
如在佈局新增ScrollView,或輸入控制元件屬於RecycleView/ListView某一項
優點:1.不會把標題欄頂出當前佈局;
2.有多項輸入時,當前輸入框下面的輸入框可上下滑動輸入
缺點:
1.需要介面本身可調整尺寸;
2. 全屏時失效
1.Activity主視窗尺寸無法調整;
2.Activity全屏
3.android5.0以上通過style設定沉浸式狀態列模式而不設定fitSystemWindow為true
非全屏或是非沉浸式狀態列輸入介面,輸入框比較多
adjustPan 頁面不會重新佈局,當前輸入框和鍵盤會直接將當前輸入框以上介面整體向上平移,這樣即使介面包含標題欄,也會被頂上去 優點: 使用簡單,不需要介面本身可調整尺寸,不會有失效情況
缺點:
會把標題欄頂出當前佈局;有多項輸入時,當前輸入框下面的輸入框無法輸入,必須收起鍵盤顯示輸入框再輸入
有少量輸入項,且輸入量居介面上方
fitsSystemWindows 如果多個View設定了fitsSystemWindows=”true”,只有初始的view起作用,都是從第一個設定了fitsSystemWindows的view開始計算padding 優點:使用簡單,需要沉浸式狀態列的介面,不需要自己計算padding狀態列的高度
缺點:
使用有限制
1.View 的其他 padding 值被重新改寫了
2.手機系統版本>=android 4.4
1.介面全屏
2.設定介面主題為沉浸式狀態列
  • adjustResize失效情況:activity設定了全屏屬性指Theme.Light.NotittleBar.Fullscreen(鍵盤彈起時會將標題欄也推上去)或者設定了activity對應的主題中android:windowTranslucentStatus屬性,設定方式為:android:windowTranslucentStatus=true,這時如果對應的頁面上含有輸入框,將會導致點選輸入框時軟鍵盤彈出後鍵盤覆蓋輸入框,導致輸入框看不見。
  • fitsSystemWindows=”true”,只有初始的view起作用:如果在佈局中不是最外層控制元件設定fitsSystemWindows=”true”, 那麼設定的那個控制元件高度會多出一個狀態列高度。若有多個view設定了,因第一個view已經消耗掉insect,其他view設定了也會被系統忽略。

假設原始介面是一個LinearLayout包含若干EditText,如下圖所示,在分別使用兩種屬性時的表現。

這裡寫圖片描述

1、adjustPan

整個介面向上平移,使輸入框露出,它不會改變介面的佈局;介面整體可用高度還是螢幕高度,這個可以通過下面的截圖看出,如點選輸入框6,輸入框會被推到鍵盤上方,但輸入框1被頂出去了,如果介面包含標題欄,也會被頂出去。

這裡寫圖片描述

2、adjustResize

需要介面的高度是可變的,或者說Activity主視窗的尺寸是可以調整的,如果不能調整,則不會起作用。
例如:Activity的xml佈局中只有一個LinearLayout包含若干EditText,在Activity的AndroidMainfest.xml中設定android:windowSoftInputMode=”adjustResize”屬性,點選輸入框6, 發現軟鍵盤擋住了輸入框6,並沒有調整,如下圖所示:

這裡寫圖片描述

但使用這兩種屬性,我們可以總結以下幾點:
1) 使用adjustPan, 如果需要輸入的項比較多時,點選輸入框,當前輸入項會被頂到軟鍵盤上方,但若當前輸入框下面還有輸入項時,卻需要先收起鍵盤,再點選相應的輸入項才能輸入。這樣操作太繁瑣了,對於使用者體驗不大好;
2) adjustResize的使用,需要介面本身可顯示的視窗內容能調整,可結合scrollview使用;

方法二:在介面最外層佈局包裹ScrollView

1、只使用ScrollView

在相應介面的xml佈局中,最外層新增一個ScrollView,不在AndroidMainfest.xml中設定任何android:windowSoftInputMode屬性,此時點選輸入框,輸入框均不會被軟鍵盤檔住。即使當前輸入框下方也有輸入框,在鍵盤顯示的情況下,也可以通過上下滑動介面來輸入,而不用先隱藏鍵盤,點選下方輸入框,再顯示鍵盤輸入。
我們可以根據Android Studio的Inspect Layout工具來檢視介面真正佔用的佈局高度,工具在
這裡寫圖片描述

通過該工具,我們看到:
介面真正能用的高度=螢幕高度-狀態列高度-軟鍵盤高度
介面中藍框是真正介面所用的高度:

這裡寫圖片描述

2、ScrollView+adjustPan

我們再在該類的AndroidMainfest.xml中設定windowSoftInputMode屬性為adjustPan,

 <activity android:name=".TestInputActivity"
            android:windowSoftInputMode="adjustPan">
  • 1
  • 2

發現當前輸入框不會被擋住,但是輸入框比較多時,在有鍵盤顯示時,介面上下滑動,但只能滑動部分,且如果輸入框在介面靠下方時,點選輸入框,標題欄也會被頂出去,如下圖所示:
這裡寫圖片描述

我們藉助Inspect Layout工具檢視此設定佈局可用高度,從下圖可以看出,此時佈局可用高度是螢幕的高度,上下滑動也只是此屏的高度,在輸入框9以下的輸入框滑不出來,向上滑動,也只能滑到輸入框1。
這裡寫圖片描述

3、ScrollView+adjustResize

我們前面說過adjustResize的使用必須介面佈局高度是可變的,如最外層套個ScrollView或是介面可收縮的,才起作用。這裡在該類的AndroidMainfest.xml中設定windowSoftInputMode屬性為adjustResize,

 <activity android:name=".TestInputActivity"
            android:windowSoftInputMode="adjustResize">
  • 1
  • 2

發現效果和1不設定任何windowSoftInputMode屬性類似,其使用高度也是:螢幕高度-狀態列高度-軟鍵盤高度
這裡寫圖片描述
我們再來看看windowSoftInputMode預設屬性值stateUnspecified:
這裡寫圖片描述
可以看出,系統將選擇合適的狀態,也就是在介面最外層包含一層ScrollView時,設定預設屬性值stateUnspecified其實就是adjustResize屬性。

但以下兩方面無法滿足需求:

1) 當Activity設定成全屏fullscreen模式時或是使用沉浸式狀態列時,介面最外層包裹 ScrollView,當輸入框超過一屏,當前輸入框下面的輸入框並不能上下滑動來輸入,情況類似於ScrollView+adjustPan,只能滑動部分,通過Inspect Layout也可以看到,介面可用高度是整個螢幕高度,並不會進行調整高度。即使設定adjustResize,也不起作用。
2) 如果是類似於註冊介面或是登入介面,鍵盤會擋住輸入框下面的登入按鈕。

沉浸式狀態列/透明狀態列情況下

自android系統4.4(API>=19)就開始支援沉浸式狀態列,當使用覺System windows(系統視窗),顯示系統一些屬性和操作區域,如 最上方的狀態及沒有實體按鍵的最下方的虛擬導航欄。
android:fitsSystemWindows=“true”會使得螢幕上的可佈局空間位於狀態列下方與導航欄上方

方法三:使用scrollTo方法,當鍵盤彈起時,讓介面整體上移;鍵盤收起,讓介面整體下移

使用場景:針對介面全屏或是沉浸式狀態列,輸入框不會被鍵盤遮擋。主要用於一些登入介面,或是需要把介面整體都頂上去的場景。

1、主要實現步驟:

(1) 獲取Activity佈局xml的最外層控制元件,如xml檔案如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/main"
    tools:context="com.example.liubin1.softkeyboardhelper.MainActivity">

    <EditText
        android:id="@+id/name"
        android:hint="請輸入使用者名稱:"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        />
    <EditText
        android:id="@+id/pas"
        android:layout_below="@id/name"
        android:hint="請輸入密碼:"
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        />
    <Button
        android:id="@+id/login_btn"
        android:layout_below="@id/rpas"
        android:layout_centerHorizontal="true"
        android:text="登入"
        android:layout_width="180dp"
        android:layout_height="50dp" />
</RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

先獲取到最外層控制元件

RelativeLayout main = (RelativeLayout) findViewById(R.id.main);
  • 1

(2) 獲取到最後一個控制元件,如上面的xml檔案,最後一個控制元件是Button

Button login_btn = (Button) findViewById(R.id.login_btn);
  • 1

(3) 給最外層控制元件和最後一個控制元件新增監聽事件

//在Activity的onCreate裡新增如下方法
addLayoutListener(main,login_btn);
/**   
     * addLayoutListener方法如下
     * @param main 根佈局
     * @param scroll 需要顯示的最下方View
     */
    public void addLayoutListener(final View main, final View scroll) {
        main.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Rect rect = new Rect();
                //1、獲取main在窗體的可視區域
                main.getWindowVisibleDisplayFrame(rect);
                //2、獲取main在窗體的不可視區域高度,在鍵盤沒有彈起時,main.getRootView().getHeight()調節度應該和rect.bottom高度一樣
                int mainInvisibleHeight = main.getRootView().getHeight() - rect.bottom;
                int screenHeight = main.getRootView().getHeight();//螢幕高度
                //3、不可見區域大於螢幕本身高度的1/4:說明鍵盤彈起了
                if (mainInvisibleHeight > screenHeight / 4) {
                    int[] location = new int[2];
                    scroll.getLocationInWindow(location);
                    // 4、獲取Scroll的窗體座標,算出main需要滾動的高度
                    int srollHeight = (location[1] + scroll.getHeight()) - rect.bottom;
                    //5、讓介面整體上移鍵盤的高度
                    main.scrollTo(0, srollHeight);
                } else {
                //3、不可見區域小於螢幕高度1/4時,說明鍵盤隱藏了,把介面下移,移回到原有高度
                    main.scrollTo(0, 0);
                }
            }
        });
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

2、實現原理:

此方法通過監聽Activity最外層佈局控制元件來檢測軟鍵盤是否彈出,然後去手動呼叫控制元件的scrollTo方法達到調整佈局目的。

具體實現程式碼見demo中的LoginActivity類。

3、弊端:

此種方法需要在當前介面寫比較多的程式碼,在某些手機上,若輸入時,軟鍵盤高度是可變的,如中英文切換,高度變化時,會發現適配的不大好。如下圖:
這裡寫圖片描述
從上圖可以看出,如果鍵盤高度變化,鍵盤還是會擋住登入按鈕。

方法四:適配鍵盤高度變化情況,當鍵盤彈起時,讓介面整體上移;鍵盤收起,讓介面整體下移

此方法主要是通過在需要移動的控制元件外套一層scrollView,同時最佈局最外層使用自定義view監聽鍵盤彈出狀態,計算鍵盤高度,再進行計算需要移動的位置,這個和方法三有點類似,但能適配鍵盤高度變化情況。

實現步驟

(1) 先寫自定義View,實時臨聽介面鍵盤彈起狀態,計算鍵盤高度

public class KeyboardLayout extends FrameLayout {

    private KeyboardLayoutListener mListener;
    private boolean mIsKeyboardActive = false; //輸入法是否啟用
    private int mKeyboardHeight = 0; // 輸入法高度

    public KeyboardLayout(Context context) {
        this(context, null, 0);
    }

    public KeyboardLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public KeyboardLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 監聽佈局變化
        getViewTreeObserver().addOnGlobalLayoutListener(new KeyboardOnGlobalChangeListener());
    }

    public void setKeyboardListener(KeyboardLayoutListener listener) {
        mListener = listener;
    }

    public KeyboardLayoutListener getKeyboardListener() {
        return mListener;
    }

    public boolean isKeyboardActive() {
        return mIsKeyboardActive;
    }

    /**
     * 獲取輸入法高度
     *
     * @return
     */
    public int getKeyboardHeight() {
        return mKeyboardHeight;
    }

    public interface KeyboardLayoutListener {
        /**
         * @param isActive       輸入法是否啟用
         * @param keyboardHeight 輸入法面板高度
         */
        void onKeyboardStateChanged(boolean isActive, int keyboardHeight);
    }

    private class KeyboardOnGlobalChangeListener implements ViewTreeObserver.OnGlobalLayoutListener {

        int mScreenHeight = 0;

        private int getScreenHeight() {
            if (mScreenHeight > 0) {
                return mScreenHeight;
            }
            mScreenHeight = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
                    .getDefaultDisplay().getHeight();
            return mScreenHeight;
        }

        @Override
        public void onGlobalLayout() {
            Rect rect = new Rect();
            // 獲取當前頁面視窗的顯示範圍
            ((Activity) getContext()).getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
            int screenHeight = getScreenHeight();
            int keyboardHeight = screenHeight - rect.bottom; // 輸入法的高度
            boolean isActive = false;
            if (Math.abs(keyboardHeight) > screenHeight / 4) {
                isActive = true; // 超過螢幕五分之一則表示彈出了輸入法
                mKeyboardHeight = keyboardHeight;
            }
            mIsKeyboardActive = isActive;
            if (mListener != null) {
                mListener.onKeyboardStateChanged(isActive, keyboardHeight);
            }
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

(2) xml檔案編寫,在介面最外層套上自定義view,在需要滾動的控制元件外層新增scrollView

<com.example.smilexie.softboradblockedittext.util.KeyboardLayout
        android:id="@+id/main_ll"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@mipmap/login_bg"
        android:orientation="vertical">

        <ScrollView
            android:id="@+id/login_ll"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

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

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="50dp"
                    android:layout_marginRight="50dp"
                    android:layout_marginTop="200dp"
                    android:background="@mipmap/login_input_field_icon"
                    android:orientation="horizontal">

                    <ImageView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center_vertical"
                        android:layout_marginLeft="20dp"
                        android:background="@mipmap/login_yonghuming_icon" />

                    <EditText
                        android:id="@+id/ui_username_input"
                        style="@style/editext_input_style"
                        android:layout_marginLeft="40dp"
                        android:layout_marginRight="20dp"
                        android:background="@null"
                        android:hint="@string/login_hint_username"
                        android:imeOptions="actionNext"
                        android:textColor="@android:color/white"
                        android:textColorHint="@android:color/white">

                        <requestFocus />
                    </EditText>
                </LinearLayout>


                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="50dp"
                    android:layout_marginRight="50dp"
                    android:layout_marginTop="20dp"
                    android:background="@mipmap/login_input_field_icon"
                    android:orientation="horizontal">

                    <ImageView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center_vertical"
                        android:layout_marginLeft="20dp"
                        android:background="@mipmap/login_mima_icon" />


                    <EditText
                        android:id="@+id/ui_password_input"
                        style="@style/editext_input_style"
                        android:layout_marginLeft="40dp"
                        android:layout_marginRight="20dp"
                        android:background="@null"
                        android:hint="@string/login_hint_pwd"
                        android:imeOptions="actionDone"
                        android:inputType="textPassword"
                        android:textColor="@android:color/white"
                        android:textColorHint="@android:color/white"></EditText>
                </LinearLayout>

                <Button
                    android:id="@+id/login_btn"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="50dp"
                    android:layout_marginRight="50dp"
                    android:layout_marginTop="20dp"
                    android:background="@mipmap/login_button_bg_icon"
                    android:text="@string/login"
                    android:textColor="@color/titlebar_main_color"
                    android:textSize="@dimen/font_normal" />
            </LinearLayout>
        </ScrollView>

    </com.example.smilexie.softboradblockedittext.util.KeyboardLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

(3) Activity呼叫,自定義view控制元件新增鍵盤響應,在鍵盤變化時呼叫scrollView的smoothScrollTo去滾動介面

 /**
     * 監聽鍵盤狀態,佈局有變化時,靠scrollView去滾動介面
     */
    public void addLayoutListener() {
        bindingView.mainLl.setKeyboardListener(new KeyboardLayout.KeyboardLayoutListener() {
            @Override
            public void onKeyboardStateChanged(boolean isActive, int keyboardHeight) {
                Log.e("onKeyboardStateChanged", "isActive:" + isActive + " keyboardHeight:" + keyboardHeight);
                if (isActive) {
                    scrollToBottom();
                }
            }
        });
    }

    /**
     * 彈出軟鍵盤時將SVContainer滑到底
     */
    private void scrollToBottom() {

        bindingView.loginLl.postDelayed(new Runnable() {

            @Override
            public void run() {
                bindingView.loginLl.smoothScrollTo(0, bindingView.loginLl.getBottom() + SoftKeyInputHidWidget.getStatusBarHeight(LoginActivityForDiffkeyboardHeight.this));
            }
        }, 100);

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

具體實現程式碼見demo中的LoginActivityForDiffkeyboardHeight類。實現效果如下:
這裡寫圖片描述
可以看到鍵盤高度變化了,也不會影響介面佈局

方法五:監聽Activity頂層View,判斷軟鍵盤是否彈起,對介面重新繪製

使用場景:針對介面全屏或是沉浸式狀態列,介面包含比較多輸入框,介面即使包裹了一層ScrollView,在鍵盤顯示時,當前輸入框下面的輸入不能通過上下滑動介面來輸入。

感謝下面提出評論的同學,指出此方法的不適配問題,之前寫的博文在華為小米手機上確實有不適配情況,在輸入時,鍵盤有時會錯亂,現在已加入適配。

一、實現步驟:

1、把SoftHideKeyBoardUtil類複製到專案中;
2、在需要使用的Activity的onCreate方法中新增:SoftHideKeyBoardUtil.assistActivity(this);即可。

二、實現原理:

SoftHideKeyBoardUtil類具體程式碼如下:

/**
 * 解決鍵盤檔住輸入框
 * Created by SmileXie on 2017/4/3.
 */

public class SoftHideKeyBoardUtil {
    public static void assistActivity (Activity activity) {
        new SoftHideKeyBoardUtil(activity);
    }
    private View mChildOfContent;
    private int usableHeightPrevious;
    private FrameLayout.LayoutParams frameLayoutParams;
    //為適應華為小米等手機鍵盤上方出現黑條或不適配
    private int contentHeight;//獲取setContentView本來view的高度
    private boolean isfirst = true;//只用獲取一次
    private  int statusBarHeight;//狀態列高度
    private SoftHideKeyBoardUtil(Activity activity) {
   //1、找到Activity的最外層佈局控制元件,它其實是一個DecorView,它所用的控制元件就是FrameLayout
        FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
        //2、獲取到setContentView放進去的View
        mChildOfContent = content.getChildAt(0);
        //3、給Activity的xml佈局設定View樹監聽,當佈局有變化,如鍵盤彈出或收起時,都會回撥此監聽  
          mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        //4、軟鍵盤彈起會使GlobalLayout發生變化
            public void onGlobalLayout() {
            if (isfirst) {
                    contentHeight = mChildOfContent.getHeight();//相容華為等機型
                    isfirst = false;
                }
                //5、當前佈局發生變化時,對Activity的xml佈局進行重繪
                possiblyResizeChildOfContent();
            }
        });
        //6、獲取到Activity的xml佈局的放置引數
        frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
    }

    // 獲取介面可用高度,如果軟鍵盤彈起後,Activity的xml佈局可用高度需要減去鍵盤高度  
    private void possiblyResizeChildOfContent() {
        //1、獲取當前介面可用高度,鍵盤彈起後,當前介面可用佈局會減少鍵盤的高度
        int usableHeightNow = computeUsableHeight();
        //2、如果當前可用高度和原始值不一樣
        if (usableHeightNow != usableHeightPrevious) {
            //3、獲取Activity中xml中佈局在當前介面顯示的高度
            int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
            //4、Activity中xml佈局的高度-當前可用高度
            int heightDifference = usableHeightSansKeyboard - usableHeightNow;
            //5、高度差大於螢幕1/4時,說明鍵盤彈出
            if (heightDifference > (usableHeightSansKeyboard/4)) {
                // 6、鍵盤彈出了,Activity的xml佈局高度應當減去鍵盤高度
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
                    frameLayoutParams.height = usableHeightSansKeyboard - heightDifference + statusBarHeight;
                } else {
                    frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
                }
            } else {
                frameLayoutParams.height = contentHeight;
            }
            //7、 重繪Activity的xml佈局
            mChildOfContent.requestLayout();
            usableHeightPrevious = usableHeightNow;
        }
    }
    private int computeUsableHeight() {
        Rect r = new Rect();
        mChildOfContent.getWindowVisibleDisplayFrame(r);
        // 全屏模式下:直接返回r.bottom,r.top其實是狀態列的高度
        return (r.bottom - r.top);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

它的實現原理主要是:
(1) 找到Activity的最外層佈局控制元件,我們知道所有的Activity都是DecorView,它就是一個FrameLayout控制元件,該控制元件id是系統寫死叫R.id.content,就是我們setContentView時,把相應的View放在此FrameLayout控制元件裡

FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
  • 1

所以content.getChildAt(0)獲取到的mChildOfContent,也就是我們用setContentView放進去的View。
(2) 給我們的Activity的xml佈局View設定一個Listener監聽

mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener({ 
        possiblyResizeChildOfContent();
});
  • 1
  • 2
  • 3

View.getViewTreeObserver()可以獲取一個ViewTreeObserver物件——它是一個觀察者,用以監聽當前View樹所發生的變化。這裡所註冊的addOnGlobalLayoutListener,就是會在當前的View樹的全域性佈局(GlobalLayout)發生變化、或者其中的View可視狀態有變化時,進行通知回撥。『軟鍵盤彈出/隱 』都能監聽到。
(3) 獲取當前介面可用高度

private int computeUsableHeight() {
    Rect rect = new Rect();
    mChildOfContent.getWindowVisibleDisplayFrame(rect);
    // rect.top其實是狀態列的高度,如果是全屏主題,直接 return rect.bottom就可以了
    return (rect.bottom - rect.top);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如下圖所示:
這裡寫圖片描述
(4) 重設高度, 我們計算出的可用高度,是目前在視覺效果上能看到的介面高度。但當前介面的實際高度是比可用高度要多出一個軟鍵盤的距離的。

具體實現程式碼見demo中的TransStatusbarWisthAssistActivity類。

注意:如果既使用了沉浸式狀態列,又加了fitSystetemWindow=true屬性,就需要在AndroidMainfest.xml註冊Activity的地方新增上以下屬性。因為你兩種都用,系統不知道用哪種了。fitSystetemWindow已經有resize螢幕的作用。

android:windowSoftInputMode="stateHidden|adjustPan"
  • 1

通過上面的這種方法,一般佈局輸入鍵盤擋住輸入框的問題基本都能解決。即使介面全屏或是沉浸式狀態列情況。

總結:

下面對上面幾種方法進行對比:

  • 方法一:優點:使用簡單,只需在Activity的AndroidMainfest.xml中設定windowSoftInput屬性即可。
    注意點:adjustResize屬性必須要介面大小可以自身改變;
    缺點:當輸入框比較多時,當前輸入框下方的輸入框會初鍵盤擋住,須收起鍵盤再進入輸入;使用adjustPan,輸入框較多時,因它是把介面當成一個整體,只會顯示一屏的高度,會把ActionBar頂上去。

  • 方法二:優點:使用簡單,只需在Activity的最外層佈局包裹一個ScrollView即可。
    注意點:不可使用adjustPan屬性,否則ScrollView失效;
    缺點:對於全屏時,在鍵盤顯示時,無法上下滑動介面達到輸入的目的;

  • 方法三:優點:可以解決全屏時,鍵盤擋入按鈕問題。
    缺點:只要有此需求的Activity均需要獲取到最外層控制元件和最後一個控制元件,監測鍵盤是否彈出,再呼叫控制元件的scrollTo方法對介面整體上移或是下移。程式碼冗餘。對於鍵盤高度變化時,適配不好。

  • 方法四:優點:可以解決全屏時,鍵盤擋入按鈕問題。
    缺點:只要有此需求的Activity均需要獲取到最外層控制元件和最後一個控制元件,佈局多出一層。

  • 方法五:優點:可以解決全屏時,鍵盤擋入輸入框問題。只需要寫一個全域性類,其他有需求的介面直接在onCreate方法裡呼叫此類的全域性方法,即可。
    缺點:多用了一個類。

綜上所述:
1) 當輸入框比較少時,介面只有一個輸入框時,可以通過方法一設定adjustPan;
2) 如果對於非全屏/非沉浸式狀態列需求,只需要使用方法二ScrollView+adjustResize;
3) 如果對於使用沉浸式狀態列,使用fitSystemWindow=true屬性,按道理android系統已經做好適配,鍵盤不會擋住輸入框;
4) 如果全屏/沉浸式狀態列介面,類似於登入介面,有需要把登入鍵鈕或是評論按鈕也頂起,如果鍵盤沒有變化需求,可以使用方法三,若需要適配鍵盤高度變化,則需要使用方法四;
5) 如果介面使用全屏或沉浸式狀態列,沒有使用fitSystemWindow=true屬性,一般如需要用到抽屈而且狀態列顏色也需要跟著變化,則選擇方法五更恰當。