InputMethodManager处理软键盘显示和隐藏的问题
Android中对软键盘的处理,可以使用showSoftInput方法进行显示操作:
public boolean showSoftInput(View view, int flags)
而对应的隐藏方法为hideSoftInputFromWindow:
public boolean hideSoftInputFromWindow(IBinder windowToken, int flags)
其中对于显示和隐藏,都有一个关键的控制参数flags。
showSoftInput(View, int)标志
1、SHOW_FORCED:表示用户强制打开输入法(如长按菜单键),一直保持打开直至只有显式关闭。
2、SHOW_IMPLICIT:表示隐式显示输入窗口,非用户直接要求。窗口可能不显示。
hideSoftInputFromWindow(IBinder, int)标志
1、HIDE_IMPLICIT_ONLY:表示如果用户未显式地显示软键盘窗口,则隐藏窗口。
2、HIDE_NOT_ALWAYS:表示软键盘窗口总是隐藏,除非开始时以SHOW_FORCED显示。
神秘标志:0
在处理显示和隐藏发过程中flags可以取值0,但是这个标志没有明确说明,其跟另外几个标志在控制显示和隐藏的关系如下:
隐藏\显示 | 0 | SHOW_IMPLICIT | SHOW_FORCED | <-显示参数 |
---|---|---|---|---|
0 | Y | Y | Y | |
HIDE_IMPLICIT_ONLY | N | Y | N | |
HIDE_NOT_ALWAYS | Y | Y | N |
Y表示可以隐藏软键盘,N表示不可以隐藏软键盘
线索
InputMethodManager.java中showXXX方法和hideXXX方法最终调用的是InputMethodManagerService.java中的方法,其中show方法如下:
public boolean showSoftInput(IInputMethodClient client, int flags, ResultReceiver resultReceiver) {
...
return showCurrentInputLocked(flags, resultReceiver);
}
而关键flags的处理在showCurrentInputLocked中的片段如下:
boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
...
if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
mShowExplicitlyRequested = true;
mShowForced = true;
} else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
mShowExplicitlyRequested = true;
}
(该方法除此之外并没有其他地方处理flags)这说明flags并不影响显示,只是在显示方法中埋个雷以便于隐藏时需要。
接着再看到隐藏方法,最终调用的是hideCurrentInputLocked:
boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mShowExplicitlyRequested || mShowForced)) {
if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
return false;
}
if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
return false;
}
// There is a chance that IMM#hideSoftInput() is called in a transient state where
// IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
// to be updated with the new value sent from IME process. Even in such a transient state
// historically we have accepted an incoming call of IMM#hideSoftInput() from the
// application process as a valid request, and have even promised such a behavior with CTS
// since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only
// IMMS#InputShown indicates that the software keyboard is shown.
// TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
final boolean shouldHideSoftInput = (mCurMethod != null) && (mInputShown ||
(mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
boolean res;
if (shouldHideSoftInput) {
// The IME will report its visible state again after the following message finally
// delivered to the IME process as an IPC. Hence the inconsistency between
// IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
// the final state.
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
res = true;
} else {
res = false;
}
if (mHaveConnection && mVisibleBound) {
mContext.unbindService(mVisibleConnection);
mVisibleBound = false;
}
mInputShown = false;
mShowRequested = false;
mShowExplicitlyRequested = false;
mShowForced = false;
return res;
}
可以看到在隐藏方法中对flags的处理如下:
if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mShowExplicitlyRequested || mShowForced)) {
if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
return false;
}
if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
return false;
}
变量对应关系如下:
SHOW_FORCED -> mShowExplicitlyRequested/mShowForced -> 0才不会被拦截:可以隐藏
0 -> mShowExplicitlyRequested -> 0和SHOW_FORCED不会被拦截 :可以隐藏
SHOW_IMPLICIT -> 无 -> 都不拦截:可以隐藏
这也正好说明了在显示和隐藏过程中使用0的厉害之处。
综上,可以得到显示和隐藏的可靠方式为:
InputMethodManager imm =(InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
显示:
imm.showSoftInput(view,0);
隐藏:
imm.hideSoftInputFromWindow(view.getWindowToken(),0);
附:检测软键盘是否显示的可靠方法
public static boolean isSoftShowing(Activity a) {
if (null == a) {
return false;
}
Window w = a.getWindow();
//获取当前屏幕内容的高度
int screenHeight = w.getDecorView().getHeight();
//获取View可见区域的bottom(键盘弹出时,除了键盘的其他区域)
Rect rect = new Rect();
w.getDecorView().getWindowVisibleDisplayFrame(rect);
Display d = a.getWindowManager().getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
//这个方法获取可能不是真实屏幕的高度
d.getMetrics(metrics);
int usableHeight = metrics.heightPixels;
//获取当前屏幕的真实高度
d.getRealMetrics(metrics);
int realHeight = metrics.heightPixels;
int barHeight = realHeight > usableHeight ? realHeight - usableHeight : 0;
int leftSpace = screenHeight - rect.bottom - barHeight;
return leftSpace != 0;
}