最近项目中碰到一个问题,我们有一个界面是一个全屏的ListView,ListView中有一个HeaderView,HeaderView内部包含一个EditText。
出现的一个问题是,在某些手机上,手动隐藏软键盘之后,此时再点击EditText就无法弹出软键盘了。 就像下面这样

EditText点击弹出软键盘的逻辑在TextView的onTouchEvent里面
@Override
public boolean onTouchEvent(MotionEvent event) {
//...
if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
&& mText instanceof Spannable && mLayout != null) {
boolean handled = false;
//...
if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = InputMethodManager.peekInstance();
viewClicked(imm);
if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
handled |= imm != null && imm.showSoftInput(this, 0);
}
// The above condition ensures that the mEditor is not null
mEditor.onTouchUpEvent(event);
handled = true;
}
if (handled) {
return true;
}
}
return superResult;
}onTouchEvent方法里面最终会调用InputMethodManager.showSoftInput来展示软键盘
showSoftInput方法最终会调用到以下方法
public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
checkFocus();
synchronized (mH) {
if (mServedView != view && (mServedView == null
|| !mServedView.checkInputConnectionProxy(view))) {
return false;
}
try {
return mService.showSoftInput(mClient, flags, resultReceiver);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}调试后发现,在执行该函数的第一个if语句时返回了,原因是mServedView!=view
我们知道view既是EditText,那么mServedView是什么?
答案是ListView。
那么mServedView是在什么时候被设置的呢?
我们再来看看showSoftInput方法中的第一句代码checkFocus
public void checkFocus() {
if (checkFocusNoStartInput(false)) {
startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);
}
}
private boolean checkFocusNoStartInput(boolean forceNewFocus) {
// This is called a lot, so short-circuit before locking.
if (mServedView == mNextServedView && !forceNewFocus) {
return false;
}
final ControlledInputConnectionWrapper ic;
synchronized (mH) {
if (mServedView == mNextServedView && !forceNewFocus) {
return false;
}
//...
ic = mServedInputConnectionWrapper;
mServedView = mNextServedView;
//...
}
if (ic != null) {
ic.finishComposingText();
}
return true;
}checkFocus会调用到checkFocusNoStartInput,这里就是mServedView被赋值的地方,而它的值就是mNextServedView。
那么问题来了,mNextServedView又是什么鬼呢?mNextServedView被赋值的地方只有一个就是focusInLocked方法
void focusInLocked(View view) {
//...
mNextServedView = view;
//...
}那focusInLocked又是在什么时候被调用呢?
经调试发现,在每次Layout过程中都会被调用,而入参就是ListView。
到了这里,问题渐渐明朗,那么如何解决问题呢?
核心方案就是将mServedView设置成我们的EditText,由于在checkFocus中会将mNextServedView的值赋予mServedView,因此只需要设置mNextServedView的值为EditText就好了。
所以,我的解决方案就是。。。。反射
etCard.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
LogicUtil.showSoftInput(v, true);
}
});首先,我们确保每次点击EditText的时候都手动去展示软键盘,从而创建一个反射的切入点,然后就是。。。
try {
Class cls = Class.forName("android.view.inputmethod.InputMethodManager");
Field f = cls.getDeclaredField("mNextServedView");
f.setAccessible(true);
f.set(manager, view);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
if(manager != null && manager.isActive() && view != null){
manager.showSoftInput(view, 0);
}在调用InputMethodManager.showSoftInput方法之前通过反射设置它的mNextServedView为EditText。
再来看看效果怎么样

完美。。。。
后记:
我认为该问题的核心其实是焦点问题,但是精力有限,没有就问题继续深入下去。而且解决方案也是简单粗暴的。如果大家有更好的解决方案,欢迎分享!