文章比较长,不想看原理的话可以直接看结论。
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方法来显示软键盘有如下注意事项。
当前布局必须已经完成加载,如果还未绘制完成,则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在InputMethodManager类中并没有提供一个和showSoftInput对应的hideSoftInput方法来隐藏软键盘,而是提供了一个showSoftInputFromInputMethod相对应的hideSoftInputFromWindow方法来隐藏软键盘。幸运的是,虽然showSoftInputFromInputMethod不能正常显示软键盘,hideSoftInputFromWindow倒是能够隐藏软键盘。
InputMethodManager的hideSoftInputFromWindow方法原型为
public boolean hideSoftInputFromWindow(IBinder windowToken, int flags);
它同样有两个参数,第一个参数是一个View的windowToken。第二个参数是软键盘隐藏时的控制参数。
使用InputMethodManager的hideSoftInputFromWindow方法来隐藏软键盘有如下注意事项。
按照官方文档,第一个参数中的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方法来切换软键盘显示状态有如下注意事项。
当显示软键盘时,并不要求当前界面布局中有一个已经获取焦点的EditText,即使当前布局是完全空白的,一个View也没有(除了最外层的Layout),toggleSoftInput也能够显示软键盘。不过如果没有一个已经获取焦点的EditText,那么软键盘中的按键输入都是无效的。就像这样。
显示软键盘时,要求当前布局必须已经加载完成,如果还未绘制完成,则toggleSoftInput()方法不起作用。这点和之前调用showSoftInput()显示软键盘时描述的第5点要求是一样的。特别的,在Activity的onCreate()中执行toggleSoftInput()必须通过postDelayed的方式来延迟执行。延迟时间一般要在50ms以上。
写到这里不得不吐槽下,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);
}
view必须是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()。