文章比較長,不想看原理的話可以直接看結論。
InputMethodManager類
Android中軟鍵盤的管理主要是通過InputMethodManager類來完成的。
InputMethodManager對象的獲取方法如下。
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
獲取到InputMethodManager對象後就可以通過調用其成員方法來對軟鍵盤進行操作。不過在使用InputMethodManager對象前通常都需要判斷其是否爲null,避免運行時異常。
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
...
}
**
顯示軟鍵盤
**
Android中可以通過InputMethodManager的showSoftInput方法來顯示軟鍵盤。
InputMethodManager的showSoftInput方法原型爲
public boolean showSoftInput(View view, int flags);
它有兩個參數,第一個參數表示當前要接收軟鍵盤輸入的View,第二個參數是軟鍵盤顯示時的控制參數。
使用InputMethodManager的showSoftInput方法來顯示軟鍵盤有如下注意事項。
- 第一個參數中view必須是EditText,或者EditText的子類,如果是其他類型的View,如Button,TextView等,showSoftInput()方法不起作用。
- 第一個參數中的view必須是可以獲取焦點的(即view.isFocusable()返回true),如果不能獲取焦點,則showSoftInput()方法不起作用。EditText默認是可獲取焦點的,所以此條件一般都可以滿足。如果不滿足,可以通過view.setFocusable(true);將其設置爲可獲取焦點的View。
- 第一個參數中的view當前必須已經獲取到焦點(即view.isFocused()返回true),如果當前焦點不在該view上,則showSoftInput()方法不起作用。雖然EditText默認是可獲取焦點的,但由於一個佈局中可能會有多個控件可以獲取焦點,焦點位置不一定會恰好在EditText上,所以此條件不一定滿足。爲了讓showSoftInput()可以起作用,必須在之前showSoftInput()前先通過view.requestFocus();獲取焦點。然後再執行showSoftInput()。
- 第一個參數中的view必須是可見的,即view.getVisibility()等於View.VISIBLE,如果view是不可見的,無論view.getVisibility()是View.INVISIBLE還是View.GONE,showSoftInput()方法都不起作用。如果view是不可見的,可以先通過view.setVisibility(View.VISIBLE)將其設置爲可見。
- 當前佈局必須已經完成加載,如果還未繪製完成,則showSoftInput()方法不起作用。特別的,在Activity的onCreate()中執行showSoftInput()是不起作用的。如果要再佈局文件加載後就顯示軟鍵盤,可以通過postDelayed的方式來延遲執行showSoftInput()。延遲時間不能太短,一般要在50ms以上。代碼示例如下。
getWindow().getDecorView().postDelayed(new Runnable() {
@Override
public void run() {
InputMethodManager imm =(InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
view.requestFocus();
imm.showSoftInput(view, 0);
}
}
}, 100);
- InputMethodManager類中提供了另外一個顯示軟鍵盤的方法showSoftInputFromInputMethod,和showSoftInput不同的是,第一個參數傳入的不是一個View對象,而是一個View對象的windowToken(對一個view對象可以通過getWindowToken()的方法獲取到其windowToken)。不過實際情況是,即使上述所有條件都滿足,通過showSoftInput(view,flag)已經可以顯示軟鍵盤,但是通過showSoftInputFromInputMethod(view.getWindowToken(),
flag)仍然無法顯示軟鍵盤。 - 參照Android官方文檔,第二個參數提供一些額外的操作標記(additional operating
flags),可以取0或者SHOW_IMPLICIT,0表示什麼含義沒有說明,SHOW_IMPLICIT表示本次顯示軟鍵盤的請求不是來自用戶的直接請求,而是隱式的請求。且不說一會用數字,一會用常量名,光SHOW_IMPLICIT的說明恐怕除了這個接口的開發人員,沒人能看懂這句話是什麼意思。實際上這個參數還可以取第三個值SHOW_FORCED,直接在文檔中被遺忘了。經過試驗,這個參數的取值對軟鍵盤的顯示沒有任何影響,無論取哪一個值軟鍵盤都能夠正常顯示(即使隨便輸入一個整數,軟鍵盤都可以顯示)。實際上這個參數影響的並不是軟鍵盤的顯示,而是軟鍵盤的隱藏,會在後文中講到。
隱藏軟鍵盤
Android在InputMethodManager類中並沒有提供一個和showSoftInput對應的hideSoftInput方法來隱藏軟鍵盤,而是提供了一個showSoftInputFromInputMethod相對應的hideSoftInputFromWindow方法來隱藏軟鍵盤。幸運的是,雖然showSoftInputFromInputMethod不能正常顯示軟鍵盤,hideSoftInputFromWindow倒是能夠隱藏軟鍵盤。
InputMethodManager的hideSoftInputFromWindow方法原型爲
public boolean hideSoftInputFromWindow(IBinder windowToken, int flags);
它同樣有兩個參數,第一個參數是一個View的windowToken。第二個參數是軟鍵盤隱藏時的控制參數。
使用InputMethodManager的hideSoftInputFromWindow方法來隱藏軟鍵盤有如下注意事項。
- 第一個參數並不是指定一個View,而是一個View的windowToken。對一個view可以通過getWindowToken()的方法獲取到其windowToken。
- 按照官方文檔,第一個參數中的windowToken應當是之前請求顯示軟鍵盤的View的windowToken,也就是執行showSoftInput()時第一個參數中的View的windowToken。但是實際情況是,用任意一個當前佈局中的已經加載的View的windowToken都可以隱藏軟鍵盤,哪怕這個View被設置爲INVISIBLE或GONE。因此,如果不知道之前是誰請求顯示的軟鍵盤,可以隨便傳入一個當前佈局中存在的View的windowToken。特別的,可以傳入一個Activity的頂層View的windowToken,即getWindow().getDecorView().getWindowToken(),來隱藏當前Activity中顯示的軟鍵盤,而不用管之前調用showSoftInput()的究竟是哪個View。示例代碼如下。
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
}
這裏還要注意的是,可以隨便傳入一個當前佈局中存在的View的windowToken,並不代表可以傳入任意一個View的windowToken,如下代碼不能實現隱藏軟鍵盤。
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(new View(this).getWindowToken(), 0);
}
對新創建的view,必須將其加入到當前佈局中後纔可以用來隱藏軟鍵盤。如下代碼可以實現隱藏軟鍵盤。當然,這裏只是爲了演示這個原理,實際使用時沒有必要繞這樣一個彎。
InputMethodManager imm1 = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm1 != null) {
View v = new View(this);
ViewGroup g1 = (ViewGroup)getWindow().getDecorView();
ViewGroup g2 = (ViewGroup)g1.getChildAt(0);
g2.addView(v);
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
- 參照Android官方文檔,第二個參數同樣是提供了一些額外的操作標記(additional operating
flags),可以取0或者HIDE_IMPLICIT_ONLY,0表示什麼含義同樣是沒有說明,HIDE_IMPLICIT_ONLY表示當前的軟鍵盤應當只在其不是被用戶顯式的顯示的時候才隱藏(the soft input window should only be hidden if it was not explicitly shown by the user)。這句話雖然很拗口,但總算是有點有用的信息了。在顯示軟鍵盤時,可以使用的flag有0,SHOW_IMPLICIT和SHOW_FORCED,參照之前的描述,當顯示軟鍵盤指定flag爲SHOW_IMPLICIT時表示隱式的顯示,也就是這裏非用戶顯式的顯示,再參照這裏的描述,如果隱藏軟鍵盤時使用的flag爲HIDE_IMPLICIT_ONLY,那麼軟鍵盤只有在非用戶顯式的顯示的時候才隱藏,這意味着如果隱藏軟鍵盤時使用的flag爲HIDE_IMPLICIT_ONLY,那麼只有當顯示軟鍵盤時指定的flag爲SHOW_IMPLICIT時,軟鍵盤纔會隱藏,如果顯示軟鍵盤時指定的flag不是SHOW_IMPLICIT,而是0或者SHOW_FORCED,那麼軟鍵盤就不會隱藏。爲了更完整的看出不同flag對隱藏軟鍵盤的影響(再聲明下,無論是顯示軟鍵盤時指定的flag,還是隱藏軟鍵盤時指定的flag都只對隱藏軟鍵盤有影響,對顯示軟鍵盤無影響),
分別在調用showSoftInput()時使用三個不同的標記,以及在調用hideSoftInputFromWindow()是使用三個不同的標記(隱藏軟鍵盤時同樣還有一個HIDE_NOT_ALWAYS標記,在官方文檔中被遺忘了),對是否能夠隱藏軟鍵盤進行測試,測試結果如下。T表示可以隱藏,F表示不能隱藏。
參數 | 0 | SHOW_IMPLICIT | SHOW_FORCED |
---|---|---|---|
0 | T | T | T |
HIDE_IMPLICIT_ONLY | F | T | F |
HIDE_NOT_ALWAYS | T | T | F |
可以看到,在隱藏軟鍵盤時使用HIDE_IMPLICIT_ONLY標記,確實只有在顯示軟鍵盤時使用SHOW_IMPLICIT時纔會隱藏。此外,當隱藏軟鍵盤時使用0作爲標記,無論showSoftInput()時使用的是哪個參數,都可以隱藏軟鍵盤。
切換軟鍵盤狀態
在InputMethodManager類中還提供了一個toggleSoftInput的方法來在顯示和隱藏軟鍵盤之間切換,也就是說,如果當前軟鍵盤是隱藏的,那麼執行toggleSoftInput方法時會顯示軟鍵盤,如果當前軟鍵盤是顯示的,那麼執行toggleSoftInput方法時會隱藏軟鍵盤。
InputMethodManager的toggleSoftInput方法原型爲
public void toggleSoftInput(int showFlags, int hideFlags);
它同樣有兩個參數,第一個參數是顯示軟鍵盤時使用的標記,第二個參數是隱藏軟鍵盤時使用的標記。
使用InputMethodManager的toggleSoftInput方法來切換軟鍵盤顯示狀態有如下注意事項。
- showFlags爲顯示軟鍵盤時使用的標記,只有當前軟鍵盤處於隱藏狀態時纔會使用。hideFlags是隱藏軟鍵盤時使用的標記,只有當前軟鍵盤處於顯示狀態時纔會使用。
- showFlags和hideFlags取值範圍,以及不同取值的影響和上文分析的一樣。即showFlags和hideFlags都隻影響軟鍵盤的隱藏,不影響軟鍵盤的顯示。不同取值對軟鍵盤隱藏的影響參見上文中的表格。
當顯示軟鍵盤時,並不要求當前界面佈局中有一個已經獲取焦點的EditText,即使當前佈局是完全空白的,一個View也沒有(除了最外層的Layout),toggleSoftInput也能夠顯示軟鍵盤。不過如果沒有一個已經獲取焦點的EditText,那麼軟鍵盤中的按鍵輸入都是無效的。就像這樣。
顯示軟鍵盤時,要求當前佈局必須已經加載完成,如果還未繪製完成,則toggleSoftInput()方法不起作用。這點和之前調用showSoftInput()顯示軟鍵盤時描述的第5點要求是一樣的。特別的,在Activity的onCreate()中執行toggleSoftInput()必須通過postDelayed的方式來延遲執行。延遲時間一般要在50ms以上。
當隱藏軟鍵盤時,不需要知道之前觸發軟鍵盤顯示的View是哪一個或獲取當前佈局中任何一個View的windowToken,只要hideFlags能夠隱藏就可以。由於hideFlags爲0時總是能夠隱藏的,因此,使用toggleSoftInput(0,
0)應當是最方便的無條件隱藏軟鍵盤的方法,前提是知道當前軟鍵盤確實是處於顯示狀態。不過遺憾的是Android沒有任何API可以直接獲取到軟鍵盤的狀態是顯示還是隱藏的。
寫到這裏不得不吐槽下,Android在軟鍵盤管理上,簡直就是一坨屎,不僅API參數含義不明,文檔混亂,很多API根本就沒有效果。這點在下一篇關於獲取軟鍵盤狀態和軟鍵盤高度的文章中會有更深的體會。
**
部分源碼解讀
**
以上的分析都是從現象上入手,要想知道原因,還是要分析Android源碼。
顯示和隱藏軟鍵盤的源碼大都在InputMethodManagerService.Java文件裏。這個文件有3000多行代碼,還有很多是其他類的交互,我也沒有仔細研究,這裏只是將其在一些片段貼出來,做一個大概的分析。
showSoftInput
showSoftInput會進入到showCurrentInputLocked()方法中,這裏有這樣一段。
if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
mShowExplicitlyRequested = true;
}
if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
mShowExplicitlyRequested = true;
mShowForced = true;
}
可以看到這裏只是將showSoftInput的第二個參數flag和SHOW_IMPLICIT,SHOW_FORCED相與,根據結果對mShowExplicitlyRequested和mShowForced賦值。在showCurrentInputLocked()方法沒有其他用到flags的地方,也沒有用到mShowExplicitlyRequested和mShowForced。這就解釋了,執行showSoftInput時傳入任意的flags都不會影響軟鍵盤的顯示。
hideSoftInputFromWindow
hideSoftInputFromWindow會進入hideCurrentInputLocked()方法,在hideCurrentInputLocked()方法的開頭有這樣一段。
if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 && (mShowExplicitlyRequested || mShowForced)) {
return false;
}
if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
return false;
}
這裏的判斷用到了隱藏軟鍵盤時使用的flags,以及mShowExplicitlyRequested和mShowForced的值,如果不滿足條件就直接返回了。可以看到當flags爲HIDE_IMPLICIT_ONLY時,如果mShowExplicitlyRequested和mShowForced任意一個爲true,都會返回false。當flags爲HIDE_NOT_ALWAYS時,如果mShowForced爲true,也會返回false,當flags爲0時,兩個if條件都不滿足。這就解釋了顯示軟鍵盤時使用的flags影響的是後面隱藏軟鍵盤是否成功,以及隱藏軟鍵盤時使用0作爲flags總是能夠隱藏軟鍵盤。
結論
顯示軟鍵盤最可靠的方法如
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) {
view.requestFocus();
imm.showSoftInput(view, 0); }
w必須是VISIBLE的EditText,如果不是VISIBLE的,需要先將其設置爲VISIBLE。
當前界面必須已經加載完成,不能直接在Activity的onCreate(),onResume(),onAttachedToWindow()中使用,可以在這些方法中通過postDelayed的方式來延遲執行showSoftInput()。
隱藏軟鍵盤最方便的方法如下。
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
view可以當前佈局中已經存在的任何一個View,如果找不到可以用getWindow().getDecorView()。