5種方法完美解決android軟鍵盤擋住輸入框方法詳解
在開發(fā)中,經(jīng)常會(huì)遇到鍵盤擋住輸入框的情況,比如登錄界面或注冊(cè)界面,彈出的軟鍵盤把登錄或注冊(cè)按鈕擋住了,用戶必須把軟鍵盤收起,才能點(diǎn)擊相應(yīng)按鈕,這樣的用戶體驗(yàn)非常不好。像微信則直接把登錄按鈕做在輸入框的上面,但有很多情況下,這經(jīng)常滿足不了需求。同時(shí)如果輸入框特別多的情況下,點(diǎn)擊輸入時(shí),當(dāng)前輸入框沒被擋住,但是當(dāng)前輸入框下面的輸入框卻無法獲取焦點(diǎn),必須先把鍵盤收起,再去獲取下面輸入框焦點(diǎn),這樣用戶體驗(yàn)也非常不好,那有什么辦法呢?
系統(tǒng)的adjustResize
和adjustPan
有什么區(qū)別,他們使用時(shí)的注意事項(xiàng),有什么系統(tǒng)要求及蔽端呢?
下面對(duì)幾種在開發(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ū)別,官方的解釋是:
這兩個(gè)屬性作用都是為了調(diào)整界面使鍵盤不擋住輸入框 ,我這里對(duì)這兩種屬性使用場景、優(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主窗口尺寸無法調(diào)整; 2.Activity全屏 3.android5.0以上通過style設(shè)置沉浸式狀態(tài)欄模式而不設(shè)置fitSystemWindow為true |
非全屏或是非沉浸式狀態(tài)欄輸入界面,輸入框比較多 |
adjustPan | 頁面不會(huì)重新布局,當(dāng)前輸入框和鍵盤會(huì)直接將當(dāng)前輸入框以上界面整體向上平移,這樣即使界面包含標(biāo)題欄,也會(huì)被頂上去 | 優(yōu)點(diǎn): 使用簡單,不需要界面本身可調(diào)整尺寸,不會(huì)有失效情況 缺點(diǎn): 會(huì)把標(biāo)題欄頂出當(dāng)前布局;有多項(xiàng)輸入時(shí),當(dāng)前輸入框下面的輸入框無法輸入,必須收起鍵盤顯示輸入框再輸入 |
無 | 有少量輸入項(xiàng),且輸入量居界面上方 |
fitsSystemWindows | 如果多個(gè)View設(shè)置了fitsSystemWindows=”true”,只有初始的view起作用,都是從第一個(gè)設(shè)置了fitsSystemWindows的view開始計(jì)算padding | 優(yōu)點(diǎn):使用簡單,需要沉浸式狀態(tài)欄的界面,不需要自己計(jì)算padding狀態(tài)欄的高度 缺點(diǎn):使用有限制 |
1.View 的其他 padding 值被重新改寫了 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)的頁面上含有輸入框,將會(huì)導(dǎo)致點(diǎn)擊輸入框時(shí)軟鍵盤彈出后鍵盤覆蓋輸入框,導(dǎo)致輸入框看不見。 -
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)。
1、adjustPan
整個(gè)界面向上平移,使輸入框露出,它不會(huì)改變界面的布局;界面整體可用高度還是屏幕高度,這個(gè)可以通過下面的截圖看出,如點(diǎn)擊輸入框6,輸入框會(huì)被推到鍵盤上方,但輸入框1被頂出去了,如果界面包含標(biāo)題欄,也會(huì)被頂出去。
2、adjustResize
需要界面的高度是可變的,或者說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,并沒有調(diào)整,如下圖所示:
但使用這兩種屬性,我們可以總結(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)前輸入框下方也有輸入框,在鍵盤顯示的情況下,也可以通過上下滑動(dòng)界面來輸入,而不用先隱藏鍵盤,點(diǎn)擊下方輸入框,再顯示鍵盤輸入。
我們可以根據(jù)Android Studio的Inspect Layout工具來查看界面真正占用的布局高度,工具在
通過該工具,我們看到:
界面真正能用的高度=屏幕高度-狀態(tài)欄高度-軟鍵盤高度
界面中藍(lán)框是真正界面所用的高度:
2、ScrollView+adjustPan
我們?cè)僭谠擃惖?code>AndroidMainfest.xml中設(shè)置windowSoftInputMode
屬性為adjustPan
,
<activity android:name=".TestInputActivity" android:windowSoftInputMode="adjustPan">
發(fā)現(xiàn)當(dāng)前輸入框不會(huì)被擋住,但是輸入框比較多時(shí),在有鍵盤顯示時(shí),界面上下滑動(dòng),但只能滑動(dòng)部分,且如果輸入框在界面靠下方時(shí),點(diǎn)擊輸入框,標(biāo)題欄也會(huì)被頂出去,如下圖所示:
我們借助Inspect Layout工具查看此設(shè)置布局可用高度,從下圖可以看出,此時(shí)布局可用高度是屏幕的高度,上下滑動(dòng)也只是此屏的高度,在輸入框9以下的輸入框滑不出來,向上滑動(dòng),也只能滑到輸入框1。
3、ScrollView+adjustResize
我們前面說過adjustResize的使用必須界面布局高度是可變的,如最外層套個(gè)ScrollView
或是界面可收縮的,才起作用。這里在該類的AndroidMainfest.xml
中設(shè)置windowSoftInputMode
屬性為adjustResize
,
<activity android:name=".TestInputActivity" android:windowSoftInputMode="adjustResize">
發(fā)現(xiàn)效果和1不設(shè)置任何windowSoftInputMode
屬性類似,其使用高度也是:屏幕高度-狀態(tài)欄高度-軟鍵盤高度
我們?cè)賮砜纯?code>windowSoftInputMode默認(rèn)屬性值stateUnspecified
:
可以看出,系統(tǒng)將選擇合適的狀態(tài),也就是在界面最外層包含一層ScrollView
時(shí),設(shè)置默認(rèn)屬性值stateUnspecified
其實(shí)就是adjustResize
屬性。
但以下兩方面無法滿足需求:
1) 當(dāng)Activity設(shè)置成全屏fullscreen
模式時(shí)或是使用沉浸式狀態(tài)欄時(shí),界面最外層包裹 ScrollView
,當(dāng)輸入框超過一屏,當(dāng)前輸入框下面的輸入框并不能上下滑動(dòng)來輸入,情況類似于ScrollView+adjustPan,只能滑動(dòng)部分,通過Inspect Layout也可以看到,界面可用高度是整個(gè)屏幕高度,并不會(huì)進(jìn)行調(diào)整高度。即使設(shè)置adjustResize,也不起作用。
2) 如果是類似于注冊(cè)界面或是登錄界面,鍵盤會(huì)擋住輸入框下面的登錄按鈕。
沉浸式狀態(tài)欄/透明狀態(tài)欄情況下
自android系統(tǒng)4.4(API>=19)就開始支持沉浸式狀態(tài)欄,當(dāng)使用覺System windows(系統(tǒng)窗口),顯示系統(tǒng)一些屬性和操作區(qū)域,如 最上方的狀態(tài)及沒有實(shí)體按鍵的最下方的虛擬導(dǎo)航欄。
android:fitsSystemWindows=“true”
會(huì)使得屏幕上的可布局空間位于狀態(tài)欄下方與導(dǎo)航欄上方
方法三:使用scrollTo方法,當(dāng)鍵盤彈起時(shí),讓界面整體上移;鍵盤收起,讓界面整體下移
使用場景:針對(duì)界面全屏或是沉浸式狀態(tài)欄,輸入框不會(huì)被鍵盤遮擋。主要用于一些登錄界面,或是需要把界面整體都頂上去的場景。
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);
(2) 獲取到最后一個(gè)控件,如上面的xml文件,最后一個(gè)控件是Button
Button login_btn = (Button) findViewById(R.id.login_btn);
(3) 給最外層控件和最后一個(gè)控件添加監(jiān)聽事件
//在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ū)域高度,在鍵盤沒有彈起時(shí),main.getRootView().getHeight()調(diào)節(jié)度應(yīng)該和rect.bottom高度一樣 int mainInvisibleHeight = main.getRootView().getHeight() - rect.bottom; int screenHeight = main.getRootView().getHeight();//屏幕高度 //3、不可見區(qū)域大于屏幕本身高度的1/4:說明鍵盤彈起了 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、不可見區(qū)域小于屏幕高度1/4時(shí),說明鍵盤隱藏了,把界面下移,移回到原有高度 main.scrollTo(0, 0); } } }); } }
2、實(shí)現(xiàn)原理:
此方法通過監(jiān)聽Activity最外層布局控件來檢測軟鍵盤是否彈出,然后去手動(dòng)調(diào)用控件的scrollTo
方法達(dá)到調(diào)整布局目的。
3、弊端:
此種方法需要在當(dāng)前界面寫比較多的代碼,在某些手機(jī)上,若輸入時(shí),軟鍵盤高度是可變的,如中英文切換,高度變化時(shí),會(huì)發(fā)現(xiàn)適配的不大好。如下圖:
從上圖可以看出,如果鍵盤高度變化,鍵盤還是會(huì)擋住登錄按鈕。
方法四:適配鍵盤高度變化情況,當(dāng)鍵盤彈起時(shí),讓界面整體上移;鍵盤收起,讓界面整體下移
此方法主要是通過在需要移動(dòng)的控件外套一層scrollView
,同時(shí)最布局最外層使用自定義view監(jiān)聽鍵盤彈出狀態(tài),計(jì)算鍵盤高度,再進(jìn)行計(jì)算需要移動(dòng)的位置,這個(gè)和方法三有點(diǎn)類似,但能適配鍵盤高度變化情況。
實(shí)現(xiàn)步驟
(1) 先寫自定義View,實(shí)時(shí)臨聽界面鍵盤彈起狀態(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)聽布局變化 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)前頁面窗口的顯示范圍 ((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); } } } }
(2) xml文件編寫,在界面最外層套上自定義view,在需要滾動(dòng)的控件外層添加scrollView
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)聽布局變化 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)前頁面窗口的顯示范圍 ((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); } } } }
(3) Activity調(diào)用,自定義view控件添加鍵盤響應(yīng),在鍵盤變化時(shí)調(diào)用scrollView
的smoothScrollTo
去滾動(dòng)界面
/** * 監(jiān)聽鍵盤狀態(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)效果如下:
可以看到鍵盤高度變化了,也不會(huì)影響界面布局
方法五:監(jiān)聽Activity頂層View,判斷軟鍵盤是否彈起,對(duì)界面重新繪制
此方法的實(shí)現(xiàn)來自android中提出的issue 5497 https://code.google.com/p/android/issues/detail?id=5497
使用場景:針對(duì)界面全屏或是沉浸式狀態(tài)欄,界面包含比較多輸入框,界面即使包裹了一層ScrollView,在鍵盤顯示時(shí),當(dāng)前輸入框下面的輸入不能通過上下滑動(dòng)界面來輸入。
感謝下面提出評(píng)論的同學(xué),指出此方法的不適配問題,之前寫的博文在華為小米手機(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本來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樹監(jiān)聽,當(dāng)布局有變化,如鍵盤彈出或收起時(shí),都會(huì)回調(diào)此監(jiān)聽 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í),說明鍵盤彈出 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)寫死叫R.id.content
,就是我們setContentView
時(shí),把相應(yīng)的View放在此FrameLayout
控件里
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
所以content.getChildAt(0)
獲取到的mChildOfContent
,也就是我們用setContentView
放進(jìn)去的View。
(2) 給我們的Activity的xml布局View設(shè)置一個(gè)Listener監(jiān)聽
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener({ possiblyResizeChildOfContent(); });
View.getViewTreeObserver()
可以獲取一個(gè)ViewTreeObserver
對(duì)象——它是一個(gè)觀察者,用以監(jiān)聽當(dāng)前View樹所發(fā)生的變化。這里所注冊(cè)的addOnGlobalLayoutListener
,就是會(huì)在當(dāng)前的View樹的全局布局(GlobalLayout)發(fā)生變化、或者其中的View可視狀態(tài)有變化時(shí),進(jìn)行通知回調(diào)?!很涙I盤彈出/隱 』都能監(jiān)聽到。
(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); }
如下圖所示:
(4) 重設(shè)高度, 我們計(jì)算出的可用高度,是目前在視覺效果上能看到的界面高度。但當(dāng)前界面的實(shí)際高度是比可用高度要多出一個(gè)軟鍵盤的距離的。
注意:如果既使用了沉浸式狀態(tài)欄,又加了fitSystetemWindow=true
屬性,就需要在AndroidMainfest.xml
注冊(cè)Activity
的地方添加上以下屬性。因?yàn)槟銉煞N都用,系統(tǒng)不知道用哪種了。fitSystetemWindow
已經(jīng)有resize
屏幕的作用。
android:windowSoftInputMode="stateHidden|adjustPan"
通過上面的這種方法,一般布局輸入鍵盤擋住輸入框的問題基本都能解決。即使界面全屏或是沉浸式狀態(tài)欄情況。
總結(jié):
下面對(duì)上面幾種方法進(jìn)行對(duì)比:
-
方法一:優(yōu)點(diǎ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):使用簡單,只需在Activity的最外層布局包裹一個(gè)ScrollView即可。
注意點(diǎn):不可使用adjustPan屬性,否則ScrollView失效;
缺點(diǎn):對(duì)于全屏?xí)r,在鍵盤顯示時(shí),無法上下滑動(dòng)界面達(dá)到輸入的目的; -
方法三:優(yōu)點(diǎn):可以解決全屏?xí)r,鍵盤擋入按鈕問題。
缺點(diǎn):只要有此需求的Activity均需要獲取到最外層控件和最后一個(gè)控件,監(jiān)測鍵盤是否彈出,再調(diào)用控件的scrollTo方法對(duì)界面整體上移或是下移。代碼冗余。對(duì)于鍵盤高度變化時(shí),適配不好。 -
方法四:優(yōu)點(diǎn):可以解決全屏?xí)r,鍵盤擋入按鈕問題。
缺點(diǎn):只要有此需求的Activity均需要獲取到最外層控件和最后一個(gè)控件,布局多出一層。 -
方法五:優(yōu)點(diǎn):可以解決全屏?xí)r,鍵盤擋入輸入框問題。只需要寫一個(gè)全局類,其他有需求的界面直接在onCreate方法里調(diào)用此類的全局方法,即可。
缺點(diǎn):多用了一個(gè)類。
綜上所述:
1) 當(dāng)輸入框比較少時(shí),界面只有一個(gè)輸入框時(shí),可以通過方法一設(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)論按鈕也頂起,如果鍵盤沒有變化需求,可以使用方法三,若需要適配鍵盤高度變化,則需要使用方法四;
5) 如果界面使用全屏或沉浸式狀態(tài)欄,沒有使用fitSystemWindow=true屬性,一般如需要用到抽屈而且狀態(tài)欄顏色也需要跟著變化,則選擇方法五更恰當(dāng)。
- Android高級(jí)xml布局之輸入框EditText設(shè)計(jì)
- Android文本輸入框(EditText)輸入密碼時(shí)顯示與隱藏
- Android實(shí)現(xiàn)動(dòng)態(tài)顯示或隱藏密碼輸入框的內(nèi)容
- 解決Android軟鍵盤彈出覆蓋h5頁面輸入框問題
- Android軟鍵盤擋住輸入框的終極解決方案
- Android仿支付寶自定義密碼輸入框及安全鍵盤(密碼鍵盤)
- Android 自定義密碼輸入框?qū)崿F(xiàn)代碼
- Android實(shí)現(xiàn)常見的驗(yàn)證碼輸入框?qū)嵗a
- Android UI設(shè)計(jì)系列之自定義EditText實(shí)現(xiàn)帶清除功能的輸入框(3)
- Android自定義九宮格輸入框
相關(guān)文章
Android圖片加載利器之Picasso擴(kuò)展功能
這篇文章主要為大家詳細(xì)介紹了Android圖片加載利器之Picasso擴(kuò)展功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Android 實(shí)現(xiàn)徹底退出自己APP 并殺掉所有相關(guān)的進(jìn)程
這篇文章主要介紹了Android 實(shí)現(xiàn)徹底退出自己APP 并殺掉所有相關(guān)的進(jìn)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03Android自定義評(píng)分控件的完整實(shí)例
在Android開發(fā)中,我們經(jīng)常會(huì)用到對(duì)商家或者商品的評(píng)價(jià),運(yùn)用星星進(jìn)行打分,下面這篇文章主要給大家介紹了關(guān)于Android自定義評(píng)分控件的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05MaterialApp?Flutter?應(yīng)用全局配置與主題管理詳解
這篇文章主要為大家介紹了MaterialApp?Flutter?應(yīng)用全局配置與主題管理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Android單項(xiàng)綁定MVVM項(xiàng)目模板的方法
這篇文章主要給大家介紹了關(guān)于Android單項(xiàng)綁定MVVM項(xiàng)目模板的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Android App中制作仿MIUI的Tab切換效果的實(shí)例分享
這篇文章主要介紹了Android App中制作仿MIUI的Tab切換效果的實(shí)例分享,實(shí)現(xiàn)具有跟隨手指滾動(dòng)而滾動(dòng)功能的ViewPagerIndicator,需要的朋友可以參考下2016-04-04Android使用Gridview單行橫向滾動(dòng)顯示
這篇文章主要為大家詳細(xì)介紹了Android使用Gridview單行橫向滾動(dòng)顯示,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07