文章比较长,不想看原理的话可以直接看结论。

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方法来显示软键盘有如下注意事项。

  1. 第一个参数中view必须是EditText,或者EditText的子类,如果是其他类型的View,如Button,TextView等,showSoftInput()方法不起作用。
  2. 第一个参数中的view必须是可以获取焦点的(即view.isFocusable()返回true),如果不能获取焦点,则showSoftInput()方法不起作用。EditText默认是可获取焦点的,所以此条件一般都可以满足。如果不满足,可以通过view.setFocusable(true);将其设置为可获取焦点的View。
  3. 第一个参数中的view当前必须已经获取到焦点(即view.isFocused()返回true),如果当前焦点不在该view上,则showSoftInput()方法不起作用。虽然EditText默认是可获取焦点的,但由于一个布局中可能会有多个控件可以获取焦点,焦点位置不一定会恰好在EditText上,所以此条件不一定满足。为了让showSoftInput()可以起作用,必须在之前showSoftInput()前先通过view.requestFocus();获取焦点。然后再执行showSoftInput()。
  4. 第一个参数中的view必须是可见的,即view.getVisibility()等于View.VISIBLE,如果view是不可见的,无论view.getVisibility()是View.INVISIBLE还是View.GONE,showSoftInput()方法都不起作用。如果view是不可见的,可以先通过view.setVisibility(View.VISIBLE)将其设置为可见。
  5. 当前布局必须已经完成加载,如果还未绘制完成,则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);
  6. InputMethodManager类中提供了另外一个显示软键盘的方法showSoftInputFromInputMethod,和showSoftInput不同的是,第一个参数传入的不是一个View对象,而是一个View对象的windowToken(对一个view对象可以通过getWindowToken()的方法获取到其windowToken)。不过实际情况是,即使上述所有条件都满足,通过showSoftInput(view,flag)已经可以显示软键盘,但是通过showSoftInputFromInputMethod(view.getWindowToken(), flag)仍然无法显示软键盘。

  7. 参照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方法来隐藏软键盘有如下注意事项。

  1. 第一个参数并不是指定一个View,而是一个View的windowToken。对一个view可以通过getWindowToken()的方法获取到其windowToken。
  2. 按照官方文档,第一个参数中的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);}
  3. 参照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方法来切换软键盘显示状态有如下注意事项。

  1. showFlags为显示软键盘时使用的标记,只有当前软键盘处于隐藏状态时才会使用。hideFlags是隐藏软键盘时使用的标记,只有当前软键盘处于显示状态时才会使用。
  2. showFlags和hideFlags取值范围,以及不同取值的影响和上文分析的一样。即showFlags和hideFlags都只影响软键盘的隐藏,不影响软键盘的显示。不同取值对软键盘隐藏的影响参见上文中的表格。
  3. 当显示软键盘时,并不要求当前界面布局中有一个已经获取焦点的EditText,即使当前布局是完全空白的,一个View也没有(除了最外层的Layout),toggleSoftInput也能够显示软键盘。不过如果没有一个已经获取焦点的EditText,那么软键盘中的按键输入都是无效的。就像这样。

  4. 显示软键盘时,要求当前布局必须已经加载完成,如果还未绘制完成,则toggleSoftInput()方法不起作用。这点和之前调用showSoftInput()显示软键盘时描述的第5点要求是一样的。特别的,在Activity的onCreate()中执行toggleSoftInput()必须通过postDelayed的方式来延迟执行。延迟时间一般要在50ms以上。

  5. 当隐藏软键盘时,不需要知道之前触发软键盘显示的View是哪一个或获取当前布局中任何一个View的windowToken,只要hideFlags能够隐藏就可以。由于hideFlags为0时总是能够隐藏的,因此,使用toggleSoftInput(0, 0)应当是最方便的无条件隐藏软键盘的方法,前提是知道当前软键盘确实是处于显示状态。不过遗憾的是Android没有任何API可以直接获取到软键盘的状态是显示还是隐藏的。

写到这里不得不吐槽下,Android在软键盘管理上,简直就是一坨屎,不仅API参数含义不明,文档混乱,很多API根本就没有效果。这点在下一篇关于获取软键盘状态和软键盘高度的文章中会有更深的体会。

部分源码解读

以上的分析都是从现象上入手,要想知道原因,还是要分析Android源码。
显示和隐藏软键盘的源码大都在InputMethodManagerService.java文件里。这个文件有3000多行代码,还有很多是其他类的交互,我也没有仔细研究,这里只是将其在一些片段贴出来,做一个大概的分析。

  1. 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都不会影响软键盘的显示。

  2. 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()。

更多相关文章

  1. Android(安卓)OpenGL 文本显示 LabelMaker
  2. Android(安卓)adb devices显示no permission
  3. Graphical layout无法正确显示的解决方法
  4. 浅谈Android的BaseAdapter适配器模式
  5. android 控制软键盘显示和隐藏
  6. Android(安卓)Studio中TextView文本过长滚动显示方法
  7. 暂时只会这种导航,实时显示自己的位置,,求其他更好的方法,或api
  8. MPAndroidChart项目实战(四)——柱状图实现及X轴文字不显示问题和
  9. android8.0通知

随机推荐

  1. Android apk的安装、卸载、更新升级(通过E
  2. Android Studio导入OpenCV Android SDK
  3. android 扫雷游戏
  4. 使用Google Map API v2
  5. Android-Jni线程(三)— JNI全局回调java方
  6. Android代码设置EditText只输入数字、字
  7. Android中Display及DisplayMetrics理解
  8. Android实现下拉放大图片松手自动反弹效
  9. android 对图像进行矩阵变换(Martix)的使
  10. Android AsyncTask usage