每个人都知道要隐藏一个键盘,你需要实现:

InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);

但这里的大问题是如何隐藏键盘时,用户触摸或选择任何其他地方,不是一个EditText或softKeyboard?

我尝试在我的父活动上使用onTouchEvent(),但只有当用户在任何其他视图之外触摸并且没有滚动视图时才有效。

我试图实现一个触摸,点击,焦点监听器,但没有任何成功。

我甚至尝试实现我自己的滚动视图来拦截触摸事件,但我只能得到事件的坐标,而不是视图被单击。

有标准的方法来做这件事吗?在iPhone中,这非常简单。


当前回答

下面的代码片段只是隐藏了键盘:

public static void hideSoftKeyboard(Activity activity) {
    InputMethodManager inputMethodManager = 
        (InputMethodManager) activity.getSystemService(
            Activity.INPUT_METHOD_SERVICE);
    if(inputMethodManager.isAcceptingText()){
        inputMethodManager.hideSoftInputFromWindow(
                activity.getCurrentFocus().getWindowToken(),
                0
        );
    }
}

你可以把它放在一个实用工具类中,或者如果你在一个活动中定义它,避免使用活动参数,或者调用hideSoftKeyboard(this)。

最棘手的部分是何时调用它。你可以写一个方法,遍历活动中的每个View,并检查它是否是EditText的实例,如果它没有注册setOnTouchListener到该组件,一切都将到位。如果你想知道如何做到这一点,实际上很简单。这就是你要做的,你写一个像下面这样的递归方法,事实上你可以用它来做任何事情,比如设置自定义字体等…这是方法

public void setupUI(View view) {

    // Set up touch listener for non-text box views to hide keyboard.
    if (!(view instanceof EditText)) {
        view.setOnTouchListener(new OnTouchListener() {
            public boolean onTouch(View v, MotionEvent event) {
                hideSoftKeyboard(MyActivity.this);
                return false;
            }
        });
    }

    //If a layout container, iterate over children and seed recursion.
    if (view instanceof ViewGroup) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            View innerView = ((ViewGroup) view).getChildAt(i);
            setupUI(innerView);
        }
    }
}

这就是所有的,只是在你setContentView在你的活动之后调用这个方法。如果你想知道你要传递什么参数,它是父容器的id。给你的父容器分配一个id

<RelativeLayoutPanel android:id="@+id/parent">…< / RelativeLayout >

并调用setupUI(findViewById(R.id.parent)),仅此而已。

如果你想有效地使用它,你可以创建一个扩展的Activity并把这个方法放进去,并让应用程序中的所有其他活动扩展这个Activity并在onCreate()方法中调用它的setupUI()。

如果你使用了多个活动,请定义父布局的公共id <RelativeLayout android:id="@+id/main_parent">…< / RelativeLayout >

然后从Activity中扩展一个类,并在其OnResume()中定义setupUI(findViewById(R.id.main_parent)),并在程序中扩展这个类而不是' ' Activity


下面是上面函数的Kotlin版本:

@file:JvmName("KeyboardUtils")

fun Activity.hideSoftKeyboard() {
    currentFocus?.let {
        val inputMethodManager = ContextCompat.getSystemService(this, InputMethodManager::class.java)!!
        inputMethodManager.hideSoftInputFromWindow(it.windowToken, 0)
    }
}

其他回答

对于这个简单的要求,我发现公认的答案有点复杂。以下是对我有效的方法,没有任何故障。

findViewById(R.id.mainLayout).setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
            return false;
        }
    });

使用OnFocusChangeListener。

例如:

editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (!hasFocus) {
            hideKeyboard();
        }
    }
});

更新:你也可以覆盖onTouchEvent()在你的活动和检查触摸的坐标。如果坐标在EditText之外,则隐藏键盘。

另一个想法是覆盖onInterceptTouchEvent方法在根视图为您的活动。

触摸事件从屏幕上最前面的视图(触摸事件发生的地方)沿着调用onTouch方法的视图堆栈向下移动,直到任何视图返回true,表明触摸事件被消费。由于许多视图默认使用触摸事件(例如,EditText或TextView的情况),事件不会到达活动的根视图onTouch方法。

But, before do this traversal, the touch event travels another path, going from the root view down the view tree until it gets to the front most view. This traversal is done by calling onInterceptTouchEvent. If the method returns true, it intercepts the event... nahhh, but that is a little bit trick, I don't think you want to do that nor to know the details. What you need to know is that you can override this method on the root view for your Activity, and put there the code to hide the keyboard when necessary.

而不是遍历所有视图或重写dispatchTouchEvent。

为什么不重写onUserInteraction()的活动,这将确保键盘解散每当用户点击EditText之外。

即使EditText在scrollView中也能工作。

@Override
public void onUserInteraction() {
    if (getCurrentFocus() != null) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
    }
}

下面是fje回答的另一个变体,它解决了sosite提出的问题。

这里的思想是在Activity的dispatchTouchEvent方法中处理向下和向上操作。在按下操作时,我们会记录当前聚焦的视图(如果有的话),以及触摸是否在其中,并为以后保存这两个信息。

在向上操作中,我们首先分派,以允许另一个视图潜在地获得焦点。如果在那之后,当前聚焦的视图是最初聚焦的视图,向下触摸在那个视图内,然后我们让键盘打开。

如果当前聚焦视图与最初聚焦视图不同并且它是一个EditText,那么我们也让键盘打开。

否则我们关闭它。

综上所述,其工作原理如下:

when touching inside a currently focused EditText, the keyboard stays open when moving from a focused EditText to another EditText, the keyboard stays open (doesn't close/reopen) when touching anywhere outside a currently focused EditText that is not another EditText, the keyboard closes when long-pressing in an EditText to bring up the contextual action bar (with the cut/copy/paste buttons), the keyboard stays open, even though the UP action took place outside the focused EditText (which moved down to make room for the CAB). Note, though, that when you tap on a button in the CAB, it will close the keyboard. That may or may not be desirable; if you want to cut/copy from one field and paste to another, it would be. If you want to paste back into the same EditText, it would not be. when the focused EditText is at the bottom of the screen and you long-click on some text to select it, the EditText keeps focus and therefore the keyboard opens like you want, because we do the "touch is within view bounds" check on the down action, not the up action. private View focusedViewOnActionDown; private boolean touchWasInsideFocusedView; @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: focusedViewOnActionDown = getCurrentFocus(); if (focusedViewOnActionDown != null) { final Rect rect = new Rect(); final int[] coordinates = new int[2]; focusedViewOnActionDown.getLocationOnScreen(coordinates); rect.set(coordinates[0], coordinates[1], coordinates[0] + focusedViewOnActionDown.getWidth(), coordinates[1] + focusedViewOnActionDown.getHeight()); final int x = (int) ev.getX(); final int y = (int) ev.getY(); touchWasInsideFocusedView = rect.contains(x, y); } break; case MotionEvent.ACTION_UP: if (focusedViewOnActionDown != null) { // dispatch to allow new view to (potentially) take focus final boolean consumed = super.dispatchTouchEvent(ev); final View currentFocus = getCurrentFocus(); // if the focus is still on the original view and the touch was inside that view, // leave the keyboard open. Otherwise, if the focus is now on another view and that view // is an EditText, also leave the keyboard open. if (currentFocus.equals(focusedViewOnActionDown)) { if (touchWasInsideFocusedView) { return consumed; } } else if (currentFocus instanceof EditText) { return consumed; } // the touch was outside the originally focused view and not inside another EditText, // so close the keyboard InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow( focusedViewOnActionDown.getWindowToken(), 0); focusedViewOnActionDown.clearFocus(); return consumed; } break; } return super.dispatchTouchEvent(ev); }