1. windowSoftInputMode屬性的使用
Android使用windowSoftInputMode來控制Activity 的主窗口與包含屏幕軟鍵盤的窗口的交互方式。 該屬性的設置影響兩個方面:
- 當 Activity 成為用戶注意的焦點時軟鍵盤的狀態 — 隱藏還是可見。
- 對 Activity 主窗口所做的調整 — 是否將其尺寸調小以為軟鍵盤騰出空間,或者當窗口部分被軟鍵盤遮擋時是否平移其內容以使當前焦點可見。
該設置必須是下表所列的值之一,或者是一個“state...”值加上一個“adjust...”值的組合。 在任一組中設置多個值(例如,多個“state...”值)都會產生未定義結果。各值之間使用垂直條 (|) 分隔。 例如:
<activity android:windowSoftInputMode="stateVisible|adjustResize" . . .復制代碼
此處設置的值(“stateUnspecified”和“adjustUnspecified”除外)替換主題中設置的值。
值 | 說明 |
---|---|
"stateUnspecified" | 不指定軟鍵盤的狀態(隱藏還是可見)。 將由系統選擇合適的狀態,或依賴主題中的設置。這是對軟鍵盤行為的默認設置。 |
“stateUnchanged” | 當 Activity 轉至前臺時保留軟鍵盤最后所處的任何狀態,無論是可見還是隱藏。 |
“stateHidden” | 當用戶選擇 Activity 時 — 也就是說,當用戶確實是向前導航到 Activity,而不是因離開另一 Activity 而返回時 — 隱藏軟鍵盤。 |
“stateAlwaysHidden” | 當 Activity 的主窗口有輸入焦點時始終隱藏軟鍵盤。 |
“stateVisible” | 在正常的適宜情況下(當用戶向前導航到 Activity 的主窗口時)顯示軟鍵盤。 |
“stateAlwaysVisible” | 當用戶選擇 Activity 時 — 也就是說,當用戶確實是向前導航到 Activity,而不是因離開另一 Activity 而返回時 — 顯示軟鍵盤。 |
“adjustUnspecified” | 不指定 Activity 的主窗口是否調整尺寸以為軟鍵盤騰出空間,或者窗口內容是否進行平移以在屏幕上顯露當前焦點。 系統會根據窗口的內容是否存在任何可滾動其內容的布局視圖來自動選擇其中一種模式。 如果存在這樣的視圖,窗口將進行尺寸調整,前提是可通過滾動在較小區域內看到窗口的所有內容。這是對主窗口行為的默認設置。 |
“adjustResize” | 始終調整 Activity 主窗口的尺寸來為屏幕上的軟鍵盤騰出空間。 |
“adjustPan” | 不調整 Activity 主窗口的尺寸來為軟鍵盤騰出空間, 而是自動平移窗口的內容,使當前焦點永遠不被鍵盤遮蓋,讓用戶始終都能看到其輸入的內容。 這通常不如尺寸調正可取,因為用戶可能需要關閉軟鍵盤以到達被遮蓋的窗口部分或與這些部分進行交互。 |
系統默認值為:stateUnspecified|adjustUnspecified
上述是引用android官方文檔的說明,但是這個并不能讓我們理解所有內容。因此本次將具體探究這9個屬性是如何影響。
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"><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content" />
</LinearLayout>復制代碼
我們發現軟鍵盤沒有自動彈出,需要手動點擊EditText后,鍵盤才會彈出來。
如果采用如下布局
<?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"><ScrollViewandroid:layout_width="match_parent"android:layout_height="match_parent"><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content" /></ScrollView>
</LinearLayout>復制代碼
我們發現軟鍵盤會自動彈出。這個就很奇怪了,為什么就加了一個ScrollView,難道是因為添加了ScrollView,軟鍵盤就可以自動彈出來嗎?我們看一下如下的布局
<?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"><ScrollViewandroid:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content" /></ScrollView>
</LinearLayout>復制代碼
運行代碼可以發現,這樣軟鍵盤也會不會自動彈出來。說明軟鍵盤自動彈出和ScrollView沒有直接關系。
如果我們采用如下布局,我們發現軟鍵盤還是會自動彈出
<?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"><ScrollViewandroid:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content" /><Buttonandroid: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"><FrameLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="3"android:background="@android:color/holo_red_dark"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="這個是一個測試"android:textSize="30sp" /></FrameLayout><FrameLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="4"android:background="@android:color/holo_blue_light"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="這個是一個測試"android:textSize="30sp" /></FrameLayout><FrameLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:background="@android:color/white"><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="center" /></FrameLayout>
</LinearLayout>復制代碼
adjustResize
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 |
在上述方法中我們看到需要傳入相關flags,相關flags有如下幾個
參數 | 說明
-------------------|---
HIDE_IMPLICIT_ONLY | 表示軟鍵盤只有在用戶未明確顯示的情況才被隱藏
HIDE_NOT_ALWAYS | 表示軟鍵盤將一致隱藏,除非調用SHOW_FORCED才會顯示
SHOW_FORCED | 表示軟鍵盤強制顯示不會被關閉,除非用戶明確的要關閉
SHOW_IMPLICIT | 表示軟鍵盤接收到一個不明確的顯示請求。- 我們經常看到用戶傳入的參數為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() {@Overridepublic 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);}@Overrideprotected 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);}復制代碼
對比上述兩種方法:
- 第一種方法的優點是不管windowSoftInputMode被設置為什么,都可以實現軟鍵盤的開關鍵盤;第二種需要將上述的布局作為界面的根布局才能實現監聽
- 第一種方法的缺點是事后監聽,已當onLayout之后才會拿到監聽,這個導致界面容易出現閃屏的情況;而第二種方法是在界面onLayout之前就拿到了監聽,因此不會出現閃屏的情況。