Android手动显示和隐藏软键盘方法总结

本文主要介绍Android中通过代码手动显示和隐藏软键盘方法。文章比较长,不想看原理的话可以直接看结论。

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. 和showSoftInput()方法不同的是,使用toggleSoftInput()显示软键盘时,并不要求当前界面布局中有一个已经获取焦点的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可以直接获取到软键盘的状态是显示还是隐藏的。

部分源码解读

显示和隐藏软键盘的源码大都在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()。

发布了55 篇原创文章 · 获赞 241 · 访问量 60万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 像素格子 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览