最近项目中碰到一个问题,我们有一个界面是一个全屏的ListView
,ListView
中有一个HeaderView
,HeaderView
内部包含一个EditText
。
出现的一个问题是,在某些手机上,手动隐藏软键盘之后,此时再点击EditText
就无法弹出软键盘了。 就像下面这样
EditText
点击弹出软键盘的逻辑在TextView
的onTouchEvent
里面
@Overridepublic 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。
再来看看效果怎么样
完美。。。。
后记:
我认为该问题的核心其实是焦点问题,但是精力有限,没有就问题继续深入下去。而且解决方案也是简单粗暴的。如果大家有更好的解决方案,欢迎分享!