ListView与EditText冲突问题解决

1,425 阅读2分钟

最近项目中碰到一个问题,我们有一个界面是一个全屏的ListViewListView中有一个HeaderViewHeaderView内部包含一个EditText。 

出现的一个问题是,在某些手机上,手动隐藏软键盘之后,此时再点击EditText就无法弹出软键盘了。 就像下面这样


EditText点击弹出软键盘的逻辑在TextViewonTouchEvent里面

@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。

再来看看效果怎么样



完美。。。。



后记:

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