android全屏/沉浸式狀態(tài)欄下擎析,各種鍵盤擋住輸入框解決辦法

前言

本博客轉(zhuǎn)載自瀟瀟鳳兒 傳送門

在開(kāi)發(fā)中绎橘,經(jīng)常會(huì)遇到鍵盤擋住輸入框的情況,比如登錄界面或注冊(cè)界面繁莹,彈出的軟鍵盤把登錄或注冊(cè)按鈕擋住了,用戶必須把軟鍵盤收起,才能點(diǎn)擊相應(yīng)按鈕仅乓,這樣的用戶體驗(yàn)非常不好。像微信則直接把登錄按鈕做在輸入框的上面蓬戚,但有很多情況下夸楣,這經(jīng)常滿足不了需求。同時(shí)如果輸入框特別多的情況下子漩,點(diǎn)擊輸入時(shí)豫喧,當(dāng)前輸入框沒(méi)被擋住,但是當(dāng)前輸入框下面的輸入框卻無(wú)法獲取焦點(diǎn)幢泼,必須先把鍵盤收起紧显,再去獲取下面輸入框焦點(diǎn),這樣用戶體驗(yàn)也非常不好旭绒,那有什么辦法呢鸟妙?
系統(tǒng)的adjustResize和adjustPan有什么區(qū)別焦人,他們使用時(shí)的注意事項(xiàng),有什么系統(tǒng)要求及蔽端呢重父?

下面對(duì)幾種在開(kāi)發(fā)中常用的方法進(jìn)行總結(jié):

方法一:非透明狀態(tài)欄下使用adjustResize和adjustPan花椭,或是透明狀態(tài)欄下使用fitsSystemWindows=true屬性

主要實(shí)現(xiàn)方法:
在AndroidManifest.xml對(duì)應(yīng)的Activity里添加
android:windowSoftInputMode=”adjustPan”或是android:windowSoftInputMode=”adjustResize”屬性
這兩種屬性的區(qū)別,官方的解釋是:


這里寫(xiě)圖片描述

這兩個(gè)屬性作用都是為了調(diào)整界面使鍵盤不擋住輸入框 房午,我這里對(duì)這兩種屬性使用場(chǎng)景矿辽、優(yōu)缺點(diǎn)、注意事項(xiàng)進(jìn)行了全方面總結(jié)郭厌,不知大家平時(shí)使用時(shí)是否注意到了袋倔。

屬性 注意事項(xiàng) 優(yōu)缺點(diǎn) 失效情況 適用情況
adjustResize 需要界面本身可調(diào)整尺寸,如在布局添加ScrollView折柠,或輸入控件屬于RecycleView/ListView某一項(xiàng) 優(yōu)點(diǎn):1.不會(huì)把標(biāo)題欄頂出當(dāng)前布局宾娜;2.有多項(xiàng)輸入時(shí),當(dāng)前輸入框下面的輸入框可上下滑動(dòng)輸入 缺點(diǎn):1.需要界面本身可調(diào)整尺寸;2. 全屏?xí)r失效 1.Activity主窗口尺寸無(wú)法調(diào)整扇售;2.Activity全屏3.android5.0以上通過(guò)style設(shè)置沉浸式狀態(tài)欄模式而不設(shè)置fitSystemWindow為true 非全屏或是非沉浸式狀態(tài)欄輸入界面前塔,輸入框比較多
adjustPan 頁(yè)面不會(huì)重新布局,當(dāng)前輸入框和鍵盤會(huì)直接將當(dāng)前輸入框以上界面整體向上平移承冰,這樣即使界面包含標(biāo)題欄华弓,也會(huì)被頂上去 優(yōu)點(diǎn): 使用簡(jiǎn)單,不需要界面本身可調(diào)整尺寸困乒,不會(huì)有失效情況 缺點(diǎn): 會(huì)把標(biāo)題欄頂出當(dāng)前布局寂屏;有多項(xiàng)輸入時(shí),當(dāng)前輸入框下面的輸入框無(wú)法輸入娜搂,必須收起鍵盤顯示輸入框再輸入 無(wú) 有少量輸入項(xiàng)迁霎,且輸入量居界面上方
fitsSystemWindows 如果多個(gè)View設(shè)置了fitsSystemWindows=”true”,只有初始的view起作用,都是從第一個(gè)設(shè)置了fitsSystemWindows的view開(kāi)始計(jì)算padding 優(yōu)點(diǎn):使用簡(jiǎn)單涌攻,需要沉浸式狀態(tài)欄的界面欧引,不需要自己計(jì)算padding狀態(tài)欄的高度 缺點(diǎn):使用有限制 1.View 的其他 padding 值被重新改寫(xiě)了2.手機(jī)系統(tǒng)版本>=android 4.4 1.界面全屏2.設(shè)置界面主題為沉浸式狀態(tài)欄
  • adjustResize失效情況:activity設(shè)置了全屏屬性指Theme.Light.NotittleBar.Fullscreen(鍵盤彈起時(shí)會(huì)將標(biāo)題欄也推上去)或者設(shè)置了activity對(duì)應(yīng)的主題中android:windowTranslucentStatus屬性,設(shè)置方式為:android:windowTranslucentStatus=true恳谎,這時(shí)如果對(duì)應(yīng)的頁(yè)面上含有輸入框芝此,將會(huì)導(dǎo)致點(diǎn)擊輸入框時(shí)軟鍵盤彈出后鍵盤覆蓋輸入框,導(dǎo)致輸入框看不見(jiàn)因痛。
  • fitsSystemWindows=”true”,只有初始的view起作用:如果在布局中不是最外層控件設(shè)置fitsSystemWindows=”true”, 那么設(shè)置的那個(gè)控件高度會(huì)多出一個(gè)狀態(tài)欄高度婚苹。若有多個(gè)view設(shè)置了,因第一個(gè)view已經(jīng)消耗掉insect鸵膏,其他view設(shè)置了也會(huì)被系統(tǒng)忽略膊升。

假設(shè)原始界面是一個(gè)LinearLayout包含若干EditText,如下圖所示,在分別使用兩種屬性時(shí)的表現(xiàn)谭企。

這里寫(xiě)圖片描述

1?adjustPan

整個(gè)界面向上平移廓译,使輸入框露出评肆,它不會(huì)改變界面的布局;界面整體可用高度還是屏幕高度非区,這個(gè)可以通過(guò)下面的截圖看出瓜挽,如點(diǎn)擊輸入框6,輸入框會(huì)被推到鍵盤上方,但輸入框1被頂出去了征绸,如果界面包含標(biāo)題欄久橙,也會(huì)被頂出去。

這里寫(xiě)圖片描述

2?adjustResize

需要界面的高度是可變的管怠,或者說(shuō)Activity主窗口的尺寸是可以調(diào)整的淆衷,如果不能調(diào)整,則不會(huì)起作用渤弛。
例如:Activity的xml布局中只有一個(gè)LinearLayout包含若干EditText,在Activity的AndroidMainfest.xml中設(shè)置android:windowSoftInputMode=”adjustResize”屬性祝拯,點(diǎn)擊輸入框6, 發(fā)現(xiàn)軟鍵盤擋住了輸入框6,并沒(méi)有調(diào)整,如下圖所示:

這里寫(xiě)圖片描述

但使用這兩種屬性她肯,我們可以總結(jié)以下幾點(diǎn):

  1. 使用adjustPan, 如果需要輸入的項(xiàng)比較多時(shí)鹿驼,點(diǎn)擊輸入框,當(dāng)前輸入項(xiàng)會(huì)被頂?shù)杰涙I盤上方辕宏,但若當(dāng)前輸入框下面還有輸入項(xiàng)時(shí),卻需要先收起鍵盤砾莱,再點(diǎn)擊相應(yīng)的輸入項(xiàng)才能輸入瑞筐。這樣操作太繁瑣了,對(duì)于用戶體驗(yàn)不大好腊瑟;
  2. adjustResize的使用聚假,需要界面本身可顯示的窗口內(nèi)容能調(diào)整,可結(jié)合scrollview使用闰非;

方法二:在界面最外層布局包裹ScrollView

1?只使用ScrollView

在相應(yīng)界面的xml布局中膘格,最外層添加一個(gè)ScrollView,不在AndroidMainfest.xml中設(shè)置任何android:windowSoftInputMode屬性财松,此時(shí)點(diǎn)擊輸入框瘪贱,輸入框均不會(huì)被軟鍵盤檔住。即使當(dāng)前輸入框下方也有輸入框辆毡,在鍵盤顯示的情況下菜秦,也可以通過(guò)上下滑動(dòng)界面來(lái)輸入,而不用先隱藏鍵盤舶掖,點(diǎn)擊下方輸入框球昨,再顯示鍵盤輸入。
我們可以根據(jù)Android Studio的Inspect Layout工具來(lái)查看界面真正占用的布局高度眨攘,工具在


這里寫(xiě)圖片描述

通過(guò)該工具主慰,我們看到:
界面真正能用的高度=屏幕高度-狀態(tài)欄高度-軟鍵盤高度
界面中藍(lán)框是真正界面所用的高度:

這里寫(xiě)圖片描述

2?ScrollView+adjustPan

我們?cè)僭谠擃惖腁ndroidMainfest.xml中設(shè)置windowSoftInputMode屬性為adjustPan嚣州,

 <activity android:name=".TestInputActivity"
            android:windowSoftInputMode="adjustPan">12

發(fā)現(xiàn)當(dāng)前輸入框不會(huì)被擋住,但是輸入框比較多時(shí)共螺,在有鍵盤顯示時(shí)该肴,界面上下滑動(dòng),但只能滑動(dòng)部分璃谨,且如果輸入框在界面靠下方時(shí),點(diǎn)擊輸入框拱雏,標(biāo)題欄也會(huì)被頂出去底扳,如下圖所示:


這里寫(xiě)圖片描述

我們借助Inspect Layout工具查看此設(shè)置布局可用高度铸抑,從下圖可以看出衷模,此時(shí)布局可用高度是屏幕的高度,上下滑動(dòng)也只是此屏的高度阱冶,在輸入框9以下的輸入框滑不出來(lái)刁憋,向上滑動(dòng)木蹬,也只能滑到輸入框1镊叁。


這里寫(xiě)圖片描述

3?ScrollView+adjustResize

我們前面說(shuō)過(guò)adjustResize的使用必須界面布局高度是可變的晦譬,如最外層套個(gè)ScrollView或是界面可收縮的,才起作用卧土。這里在該類的AndroidMainfest.xml中設(shè)置windowSoftInputMode屬性為adjustResize迎瞧,

 <activity android:name=".TestInputActivity"
            android:windowSoftInputMode="adjustResize">12

發(fā)現(xiàn)效果和1不設(shè)置任何windowSoftInputMode屬性類似凶硅,其使用高度也是:屏幕高度-狀態(tài)欄高度-軟鍵盤高度


這里寫(xiě)圖片描述

我們?cè)賮?lái)看看windowSoftInputMode默認(rèn)屬性值stateUnspecified:


這里寫(xiě)圖片描述

可以看出,系統(tǒng)將選擇合適的狀態(tài)韩脑,也就是在界面最外層包含一層ScrollView時(shí)粹污,設(shè)置默認(rèn)屬性值stateUnspecified其實(shí)就是adjustResize屬性壮吩。

但以下兩方面無(wú)法滿足需求:

  1. 當(dāng)Activity設(shè)置成全屏fullscreen模式時(shí)或是使用沉浸式狀態(tài)欄時(shí),界面最外層包裹 ScrollView觉啊,當(dāng)輸入框超過(guò)一屏杠人,當(dāng)前輸入框下面的輸入框并不能上下滑動(dòng)來(lái)輸入,情況類似于ScrollView+adjustPan宋下,只能滑動(dòng)部分,通過(guò)Inspect Layout也可以看到罩引,界面可用高度是整個(gè)屏幕高度蜒程,并不會(huì)進(jìn)行調(diào)整高度。即使設(shè)置adjustResize忌锯,也不起作用偶垮。
  2. 如果是類似于注冊(cè)界面或是登錄界面似舵,鍵盤會(huì)擋住輸入框下面的登錄按鈕。

沉浸式狀態(tài)欄/透明狀態(tài)欄情況下

自android系統(tǒng)4.4(API>=19)就開(kāi)始支持沉浸式狀態(tài)欄龙助,當(dāng)使用覺(jué)System windows(系統(tǒng)窗口),顯示系統(tǒng)一些屬性和操作區(qū)域提鸟,如 最上方的狀態(tài)及沒(méi)有實(shí)體按鍵的最下方的虛擬導(dǎo)航欄称勋。
android:fitsSystemWindows=“true”會(huì)使得屏幕上的可布局空間位于狀態(tài)欄下方與導(dǎo)航欄上方

方法三:使用scrollTo方法,當(dāng)鍵盤彈起時(shí)空厌,讓界面整體上移嘲更;鍵盤收起捡硅,讓界面整體下移

使用場(chǎng)景:針對(duì)界面全屏或是沉浸式狀態(tài)欄壮韭,輸入框不會(huì)被鍵盤遮擋喷屋。主要用于一些登錄界面,或是需要把界面整體都頂上去的場(chǎng)景狱庇。

1?主要實(shí)現(xiàn)步驟:

(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="請(qǐng)輸入用戶名:"
        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="請(qǐng)輸入密碼:"
        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>

先獲取到最外層控件

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

(2) 獲取到最后一個(gè)控件浪讳,如上面的xml文件淹遵,最后一個(gè)控件是Button

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

(3) 給最外層控件和最后一個(gè)控件添加監(jiān)聽(tīng)事件

//在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在窗體的可視區(qū)域
                main.getWindowVisibleDisplayFrame(rect);
                //2辐真、獲取main在窗體的不可視區(qū)域高度,在鍵盤沒(méi)有彈起時(shí)恨闪,main.getRootView().getHeight()調(diào)節(jié)度應(yīng)該和rect.bottom高度一樣
                int mainInvisibleHeight = main.getRootView().getHeight() - rect.bottom;
                int screenHeight = main.getRootView().getHeight();//屏幕高度
                //3咙咽、不可見(jiàn)區(qū)域大于屏幕本身高度的1/4:說(shuō)明鍵盤彈起了
                if (mainInvisibleHeight > screenHeight / 4) {
                    int[] location = new int[2];
                    scroll.getLocationInWindow(location);
                    // 4?獲取Scroll的窗體坐標(biāo)钧敞,算出main需要滾動(dòng)的高度
                    int srollHeight = (location[1] + scroll.getHeight()) - rect.bottom;
                    //5?讓界面整體上移鍵盤的高度
                    main.scrollTo(0, srollHeight);
                } else {
                //3麸粮、不可見(jiàn)區(qū)域小于屏幕高度1/4時(shí),說(shuō)明鍵盤隱藏了弄诲,把界面下移齐遵,移回到原有高度
                    main.scrollTo(0, 0);
                }
            }
        });
    }
}

2?實(shí)現(xiàn)原理:

此方法通過(guò)監(jiān)聽(tīng)Activity最外層布局控件來(lái)檢測(cè)軟鍵盤是否彈出梗摇,然后去手動(dòng)調(diào)用控件的scrollTo方法達(dá)到調(diào)整布局目的。

具體實(shí)現(xiàn)代碼見(jiàn)demo中的LoginActivity類断序。

3?弊端:

此種方法需要在當(dāng)前界面寫(xiě)比較多的代碼违诗,在某些手機(jī)上较雕,若輸入時(shí)挚币,軟鍵盤高度是可變的妆毕,如中英文切換笛粘,高度變化時(shí),會(huì)發(fā)現(xiàn)適配的不大好润努。如下圖:


這里寫(xiě)圖片描述

從上圖可以看出铺浇,如果鍵盤高度變化鳍侣,鍵盤還是會(huì)擋住登錄按鈕倚聚。

方法四:適配鍵盤高度變化情況凿可,當(dāng)鍵盤彈起時(shí)枯跑,讓界面整體上移全肮;鍵盤收起,讓界面整體下移

此方法主要是通過(guò)在需要移動(dòng)的控件外套一層scrollView休建,同時(shí)最布局最外層使用自定義view監(jiān)聽(tīng)鍵盤彈出狀態(tài)测砂,計(jì)算鍵盤高度砌些,再進(jìn)行計(jì)算需要移動(dòng)的位置加匈,這個(gè)和方法三有點(diǎn)類似雕拼,但能適配鍵盤高度變化情況啥寇。

實(shí)現(xiàn)步驟

(1) 先寫(xiě)自定義View,實(shí)時(shí)臨聽(tīng)界面鍵盤彈起狀態(tài)衰絮,計(jì)算鍵盤高度

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);
        // 監(jiān)聽(tīng)布局變化
        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();
            // 獲取當(dāng)前頁(yè)面窗口的顯示范圍
            ((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; // 超過(guò)屏幕五分之一則表示彈出了輸入法
                mKeyboardHeight = keyboardHeight;
            }
            mIsKeyboardActive = isActive;
            if (mListener != null) {
                mListener.onKeyboardStateChanged(isActive, keyboardHeight);
            }
        }
    }
}

(2) xml文件編寫(xiě),在界面最外層套上自定義view镊掖,在需要滾動(dòng)的控件外層添加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>

(3) Activity調(diào)用归薛,自定義view控件添加鍵盤響應(yīng),在鍵盤變化時(shí)調(diào)用scrollView的smoothScrollTo去滾動(dòng)界面

 /**
     * 監(jiān)聽(tīng)鍵盤狀態(tài)习贫,布局有變化時(shí)苫昌,靠scrollView去滾動(dòng)界面
     */
    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();
                }
            }
        });
    }

    /**
     * 彈出軟鍵盤時(shí)將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);

    }

具體實(shí)現(xiàn)代碼見(jiàn)demo中的LoginActivityForDiffkeyboardHeight類祟身。實(shí)現(xiàn)效果如下:


這里寫(xiě)圖片描述

可以看到鍵盤高度變化了袜硫,也不會(huì)影響界面布局

方法五:監(jiān)聽(tīng)Activity頂層View婉陷,判斷軟鍵盤是否彈起官研,對(duì)界面重新繪制

此方法的實(shí)現(xiàn)來(lái)自android中提出的issue 5497 https://code.google.com/p/android/issues/detail?id=5497

使用場(chǎng)景:針對(duì)界面全屏或是沉浸式狀態(tài)欄戏羽,界面包含比較多輸入框蛛壳,界面即使包裹了一層ScrollView,在鍵盤顯示時(shí)衙荐,當(dāng)前輸入框下面的輸入不能通過(guò)上下滑動(dòng)界面來(lái)輸入。

感謝下面提出評(píng)論的同學(xué)砌函,指出此方法的不適配問(wèn)題讹俊,之前寫(xiě)的博文在華為小米手機(jī)上確實(shí)有不適配情況煌抒,在輸入時(shí)寡壮,鍵盤有時(shí)會(huì)錯(cuò)亂况既,現(xiàn)在已加入適配。

一悲靴、實(shí)現(xiàn)步驟:

1?把SoftHideKeyBoardUtil類復(fù)制到項(xiàng)目中癞尚;
2?在需要使用的Activity的onCreate方法中添加:SoftHideKeyBoardUtil.assistActivity(this);即可否纬。

二蛋褥、實(shí)現(xiàn)原理:

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;
    //為適應(yīng)華為小米等手機(jī)鍵盤上方出現(xiàn)黑條或不適配
    private int contentHeight;//獲取setContentView本來(lái)view的高度
    private boolean isfirst = true;//只用獲取一次
    private  int statusBarHeight;//狀態(tài)欄高度
    private SoftHideKeyBoardUtil(Activity activity) {
   //1?找到Activity的最外層布局控件膜廊,它其實(shí)是一個(gè)DecorView,它所用的控件就是FrameLayout
        FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
        //2?獲取到setContentView放進(jìn)去的View
        mChildOfContent = content.getChildAt(0);
        //3?給Activity的xml布局設(shè)置View樹(shù)監(jiān)聽(tīng)淫茵,當(dāng)布局有變化匙瘪,如鍵盤彈出或收起時(shí),都會(huì)回調(diào)此監(jiān)聽(tīng)  
          mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        //4?軟鍵盤彈起會(huì)使GlobalLayout發(fā)生變化
            public void onGlobalLayout() {
            if (isfirst) {
                    contentHeight = mChildOfContent.getHeight();//兼容華為等機(jī)型
                    isfirst = false;
                }
                //5?當(dāng)前布局發(fā)生變化時(shí)翁都,對(duì)Activity的xml布局進(jìn)行重繪
                possiblyResizeChildOfContent();
            }
        });
        //6?獲取到Activity的xml布局的放置參數(shù)
        frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
    }

    // 獲取界面可用高度柄慰,如果軟鍵盤彈起后税娜,Activity的xml布局可用高度需要減去鍵盤高度  
    private void possiblyResizeChildOfContent() {
        //1?獲取當(dāng)前界面可用高度敬矩,鍵盤彈起后谤绳,當(dāng)前界面可用布局會(huì)減少鍵盤的高度
        int usableHeightNow = computeUsableHeight();
        //2?如果當(dāng)前可用高度和原始值不一樣
        if (usableHeightNow != usableHeightPrevious) {
            //3?獲取Activity中xml中布局在當(dāng)前界面顯示的高度
            int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
            //4?Activity中xml布局的高度-當(dāng)前可用高度
            int heightDifference = usableHeightSansKeyboard - usableHeightNow;
            //5?高度差大于屏幕1/4時(shí)缩筛,說(shuō)明鍵盤彈出
            if (heightDifference > (usableHeightSansKeyboard/4)) {
                // 6?鍵盤彈出了瞎抛,Activity的xml布局高度應(yīng)當(dāng)減去鍵盤高度
                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其實(shí)是狀態(tài)欄的高度
        return (r.bottom - r.top);
    }
}

它的實(shí)現(xiàn)原理主要是:
(1) 找到Activity的最外層布局控件,我們知道所有的Activity都是DecorView伤提,它就是一個(gè)FrameLayout控件肿男,該控件id是系統(tǒng)寫(xiě)死叫R.id.content舶沛,就是我們setContentView時(shí)如庭,把相應(yīng)的View放在此FrameLayout控件里

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

所以content.getChildAt(0)獲取到的mChildOfContent撼港,也就是我們用setContentView放進(jìn)去的View。
(2) 給我們的Activity的xml布局View設(shè)置一個(gè)Listener監(jiān)聽(tīng)

mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener({ 
        possiblyResizeChildOfContent();
});123

View.getViewTreeObserver()可以獲取一個(gè)ViewTreeObserver對(duì)象——它是一個(gè)觀察者瘤载,用以監(jiān)聽(tīng)當(dāng)前View樹(shù)所發(fā)生的變化。這里所注冊(cè)的addOnGlobalLayoutListener惩阶,就是會(huì)在當(dāng)前的View樹(shù)的全局布局(GlobalLayout)發(fā)生變化扣汪、或者其中的View可視狀態(tài)有變化時(shí)崭别,進(jìn)行通知回調(diào)茅主。『軟鍵盤彈出/隱 』都能監(jiān)聽(tīng)到响牛。
(3) 獲取當(dāng)前界面可用高度

private int computeUsableHeight() {
    Rect rect = new Rect();
    mChildOfContent.getWindowVisibleDisplayFrame(rect);
    // rect.top其實(shí)是狀態(tài)欄的高度呀打,如果是全屏主題贬丛,直接 return rect.bottom就可以了
    return (rect.bottom - rect.top);
}123456

如下圖所示:


這里寫(xiě)圖片描述

(4) 重設(shè)高度豺憔, 我們計(jì)算出的可用高度焕阿,是目前在視覺(jué)效果上能看到的界面高度暮屡。但當(dāng)前界面的實(shí)際高度是比可用高度要多出一個(gè)軟鍵盤的距離的毅桃。

具體實(shí)現(xiàn)代碼見(jiàn)demo中的TransStatusbarWisthAssistActivity類。

注意:如果既使用了沉浸式狀態(tài)欄衫嵌,又加了fitSystetemWindow=true屬性楔绞,就需要在AndroidMainfest.xml注冊(cè)Activity的地方添加上以下屬性唇兑。因?yàn)槟銉煞N都用扎附,系統(tǒng)不知道用哪種了留夜。fitSystetemWindow已經(jīng)有resize屏幕的作用碍粥。

android:windowSoftInputMode="stateHidden|adjustPan"1

通過(guò)上面的這種方法嚼摩,一般布局輸入鍵盤擋住輸入框的問(wèn)題基本都能解決。即使界面全屏或是沉浸式狀態(tài)欄情況蜂厅。

總結(jié):

下面對(duì)上面幾種方法進(jìn)行對(duì)比:

  • 方法一:優(yōu)點(diǎn):使用簡(jiǎn)單,只需在Activity的AndroidMainfest.xml中設(shè)置windowSoftInput屬性即可。
    注意點(diǎn):adjustResize屬性必須要界面大小可以自身改變;
    缺點(diǎn):當(dāng)輸入框比較多時(shí)改橘,當(dāng)前輸入框下方的輸入框會(huì)初鍵盤擋住飞主,須收起鍵盤再進(jìn)入輸入碌识;使用adjustPan,輸入框較多時(shí)开泽,因它是把界面當(dāng)成一個(gè)整體穆律,只會(huì)顯示一屏的高度众旗,會(huì)把ActionBar頂上去。
  • 方法二:優(yōu)點(diǎn):使用簡(jiǎn)單赋秀,只需在Activity的最外層布局包裹一個(gè)ScrollView即可律想。
    注意點(diǎn):不可使用adjustPan屬性技即,否則ScrollView失效而叼;
    缺點(diǎn):對(duì)于全屏?xí)r,在鍵盤顯示時(shí)葵陵,無(wú)法上下滑動(dòng)界面達(dá)到輸入的目的液荸;
  • 方法三:優(yōu)點(diǎn):可以解決全屏?xí)r,鍵盤擋入按鈕問(wèn)題脱篙。
    缺點(diǎn):只要有此需求的Activity均需要獲取到最外層控件和最后一個(gè)控件娇钱,監(jiān)測(cè)鍵盤是否彈出,再調(diào)用控件的scrollTo方法對(duì)界面整體上移或是下移绊困。代碼冗余文搂。對(duì)于鍵盤高度變化時(shí)秤朗,適配不好煤蹭。
  • 方法四:優(yōu)點(diǎn):可以解決全屏?xí)r,鍵盤擋入按鈕問(wèn)題。
    缺點(diǎn):只要有此需求的Activity均需要獲取到最外層控件和最后一個(gè)控件疯兼,布局多出一層然遏。
  • 方法五:優(yōu)點(diǎn):可以解決全屏?xí)r,鍵盤擋入輸入框問(wèn)題吧彪。只需要寫(xiě)一個(gè)全局類待侵,其他有需求的界面直接在onCreate方法里調(diào)用此類的全局方法,即可姨裸。
    缺點(diǎn):多用了一個(gè)類秧倾。

綜上所述:

  1. 當(dāng)輸入框比較少時(shí),界面只有一個(gè)輸入框時(shí)傀缩,可以通過(guò)方法一設(shè)置adjustPan那先;
  2. 如果對(duì)于非全屏/非沉浸式狀態(tài)欄需求,只需要使用方法二ScrollView+adjustResize赡艰;
  3. 如果對(duì)于使用沉浸式狀態(tài)欄售淡,使用fitSystemWindow=true屬性,按道理android系統(tǒng)已經(jīng)做好適配慷垮,鍵盤不會(huì)擋住輸入框揖闸;
  4. 如果全屏/沉浸式狀態(tài)欄界面,類似于登錄界面料身,有需要把登錄鍵鈕或是評(píng)論按鈕也頂起汤纸,如果鍵盤沒(méi)有變化需求,可以使用方法三芹血,若需要適配鍵盤高度變化贮泞,則需要使用方法四;
  5. 如果界面使用全屏或沉浸式狀態(tài)欄幔烛,沒(méi)有使用fitSystemWindow=true屬性啃擦,一般如需要用到抽屈而且狀態(tài)欄顏色也需要跟著變化,則選擇方法五更恰當(dāng)饿悬。

代碼傳送門:https://github.com/xiewenfeng/SoftboradBlockEdittext
如果有哪寫(xiě)的不對(duì)或是不好的地方议惰,歡迎大家提出寶貴意見(jiàn),一起探討乡恕,共同進(jìn)步言询。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市傲宜,隨后出現(xiàn)的幾起案子运杭,更是在濱河造成了極大的恐慌,老刑警劉巖函卒,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辆憔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)虱咧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門熊榛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人腕巡,你說(shuō)我怎么就攤上這事玄坦。” “怎么了绘沉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵煎楣,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我车伞,道長(zhǎng)择懂,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任另玖,我火速辦了婚禮困曙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谦去。我一直安慰自己慷丽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布哪轿。 她就那樣靜靜地躺著,像睡著了一般翔怎。 火紅的嫁衣襯著肌膚如雪窃诉。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 48,954評(píng)論 1 283
  • 那天赤套,我揣著相機(jī)與錄音飘痛,去河邊找鬼。 笑死容握,一個(gè)胖子當(dāng)著我的面吹牛宣脉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播剔氏,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼塑猖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了谈跛?” 一聲冷哼從身側(cè)響起羊苟,我...
    開(kāi)封第一講書(shū)人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎感憾,沒(méi)想到半個(gè)月后蜡励,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年凉倚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了兼都。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡稽寒,死狀恐怖扮碧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瓦胎,我是刑警寧澤芬萍,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站搔啊,受9級(jí)特大地震影響柬祠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜负芋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一漫蛔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧旧蛾,春花似錦莽龟、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至病袄,卻和暖如春搂赋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背益缠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工脑奠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人幅慌。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓宋欺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親胰伍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子齿诞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評(píng)論 25 707
  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線程,因...
    小菜c閱讀 6,358評(píng)論 0 17
  • 作者簡(jiǎn)介 創(chuàng)微信公眾號(hào)郭霖 WeChat ID: guolin_blog 瀟瀟鳳兒的博客地址: http://b...
    木木00閱讀 10,752評(píng)論 1 43
  • 明天骂租,也就是2018年6月7日掌挚,新一年的高考又如期而至。其實(shí)這么一個(gè)對(duì)于很多人來(lái)說(shuō)特殊的日子菩咨,我卻總不敢確定吠式,因?yàn)?..
    演凡閱讀 142評(píng)論 0 0
  • 生活就是情節(jié)和對(duì)白陡厘,只不過(guò)比故事中的戲劇情節(jié)多了瑣碎,日子在每一天的往來(lái)熙攘中就這樣過(guò)去了特占〔谥茫可能每一個(gè)人的心...
    兼鳥(niǎo)魚(yú)枼閱讀 344評(píng)論 0 1