如何在Android上点击EditText外部后隐藏软键盘?

426

大家都知道隐藏键盘需要实现:

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

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

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

我尝试实现了一个触摸、点击、焦点监听器,但都没有成功。

我甚至尝试实现自己的滚动视图来拦截触摸事件,但我只能获得事件的坐标而无法获取所点击的视图。

是否有一种标准方法来解决这个问题?在iPhone上,这真的很容易。


我意识到ScrollView并不是问题所在,而是其中的标签。该视图是一个垂直布局,包含如下内容: TextView、EditText、TextView、EditText等等。而这些TextView会阻止EditText失去焦点并隐藏键盘。 - htafoya
你可以在这里找到 getFields() 的解决方案:https://dev59.com/e2sz5IYBdhLWcg3wlY-9 - Reto
键盘可以通过按回车键来关闭,因此我认为这是否值得努力是有问题的。 - gerrytan
5
我找到了这篇回答:https://dev59.com/7m445IYBdhLWcg3wfKcZ#28939113,是最好的一个。 - Loenix
49个回答

619
以下代码段仅将键盘隐藏起来:
public static void hideSoftKeyboard(Activity activity) {
    InputMethodManager inputMethodManager = 
        (InputMethodManager) activity.getSystemService(
            Activity.INPUT_METHOD_SERVICE);
    if(inputMethodManager.isAcceptingText()){
        inputMethodManager.hideSoftInputFromWindow(
                activity.getCurrentFocus().getWindowToken(),
                0
        );
    }
}

你可以将这段代码放在一个实用类中,如果你是在活动中定义它,避免使用activity参数,或者调用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类,并把这个方法放进去,让你应用中的所有其他活动都继承这个活动并在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)
    }
}

它的表现很好,键盘在应用程序的任何地方都会消失,但我面临的问题是,即使键盘被关闭,焦点仍然留在EditTextView上。有没有解决这个问题的方法? - Javal Nanda
4
很简单吧?我现在不懂安卓编程,如果我错了请纠正我。你可以在任何时候跟踪目前聚焦的EditText,并通过OnTouchEvent请求它失去焦点。 - Navneeth G
27
不确定是否有其他人遇到过这个问题,但是如果没有聚焦在任何输入框时调用hideSoftKeyboard会导致应用程序崩溃。你可以通过在该方法的第二行周围添加if(activity.getCurrentFocus() != null) {...}来解决此问题。 - Frank Cangialosi
14
这种方法的问题在于它假设所有其他视图都不需要为它们设置OnTouchListener。你可以将这个逻辑设置在一个ViewGroup.onInterceptTouchEvent(MotionEvent)中作为根视图。 - Alex.F
2
点击其他控件时无法正常工作,键盘仍然保持打开状态。 - mohitum
显示剩余14条评论

335

您可以通过以下步骤实现此操作:

  1. 使父视图(活动的内容视图)具有可点击和可聚焦的特性,方法是添加以下属性:

        android:clickable="true" 
        android:focusableInTouchMode="true" 
    
  2. 实现一个hideKeyboard()方法

  3.     public void hideKeyboard(View view) {
            InputMethodManager inputMethodManager =(InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
            inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
        }
    
  4. 最后,设置您的EditText的onFocusChangeListener。

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

如下面的评论所指出的那样,如果父视图是ScrollView,则可能无法正常工作。对于这种情况,可以在ScrollView下直接添加可点击和触摸模式下具有焦点功能的视图。


76
我认为这是正确的答案。代码更简洁,没有不必要的迭代... - gattshjoty
19
我很喜欢这个答案。需要注意的一点是,当我在根ScrollView元素中添加clickablefocusableInTouchMode时,它对我没有起作用。我必须添加到我的EditText的直接父级,即LinearLayout中。 - Adam Johns
11
对我而言完美地工作。但是,如果您有两个EditText小部件,需要确保正确处理它们的onfocus,否则您将不必要地切换键盘隐藏。 - Marka A
1
可以工作,但从一个EditText切换到另一个EditText时,键盘会隐藏和弹出(观察三角形或返回按钮时可以注意到)。 - Nikola
7
@Shrikant - 当我使用多个编辑文本框时也发现了闪烁问题。我将onFocusChangeListener设置在父元素上,而不是每个编辑文本框上,并将条件更改为if (hasFocus) { hideKeyboard(v); } 在编辑文本框之间切换时,我没有再注意到闪烁问题。 - manisha
显示剩余14条评论

146

只需在Activity中覆盖以下代码即可。

 @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (getCurrentFocus() != null) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
    }
    return super.dispatchTouchEvent(ev);
}

更新:

如果有人需要此答案的 Kotlin 版本:

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
    if (currentFocus != null) {
        val imm = activity!!.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        imm.hideSoftInputFromWindow(activity!!.currentFocus!!.windowToken, 0)
    }
    return super.dispatchTouchEvent(ev)
}

11
简单的解决方案,加入活动并且它会照顾到片段。 - Rohit Maurya
3
卓越的解决方案 - Ahmed Adel Ismail
1
你帮我解决了关闭键盘时屏幕闪烁的问题,赞一个! - Dimas Mendes
19
太棒了,但这似乎过于美好了:简单、非常短小精悍而且还能够正常工作...不过,问题出现了:当键盘显示时,每次我们触摸请求键盘的EditText时,它会自动上下移动。 - Chrysotribax
1
你能做些什么来解决Chrysotribax提到的问题吗?基本上,我更喜欢这个短答案,而不是那些投票更多的荒谬复杂的解答。然而,自动向下滚动和向上滚动是一个小问题。 - VanessaF
显示剩余6条评论

66

我觉得被接受的答案有点复杂。

这是我的解决方案。向您的主布局添加一个 OnTouchListener,例如:

findViewById(R.id.mainLayout).setOnTouchListener(this)

并将以下代码放在onTouch方法中。

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

这样你就不必遍历所有视图。


@roepit - 我试图将一个布局转换为视图时出现了ClassCastException异常。我是否漏掉了什么? - katzenhut
你能在某个地方引用你的代码吗?如果我无法查看你的布局和活动/片段等代码,我就无法确定问题出在哪里。 - roepit
目前最佳答案,但仍在努力理解它的工作原理。 - Pol
如果您点击应用程序的标题栏,这会起作用吗? - user1506104
1
这对于隐藏键盘非常有效!请注意,这实际上并没有取消 EditText 的焦点,它只是隐藏了键盘。要同时取消 EditText 的焦点,请在父视图的 xml 中添加 android:onClick="stealFocusFromEditTexts",然后在其活动中添加 public void stealFocusFromEditTexts(View view) {}。单击方法不需要执行任何操作,它只需要存在于父视图中以使其可聚焦/可选择,这对于从子 EditText 中窃取焦点是必要的。 - Jacob R
谢谢,这是这里最简单的答案。 - Abdulrahman Abdelkader

44

我有另外一种方法可以隐藏键盘:

InputMethodManager imm = (InputMethodManager) getSystemService(
    Activity.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);

showFlag的位置传递HIDE_IMPLICIT_ONLY,在hiddenFlag的位置传递0。 这将强制关闭软键盘。


3
谢谢,它有效了……尤其是在我从对话框编辑文本中获取值并关闭对话框时,我已经尝试过但无效。 - PankajAndroid
谢谢,这个代码仅有的作用是比上面那个干净多了!+1 - Edmond Tamas
一切都按预期运行,感谢您的解决方案 +1 - tryp
这对我来说运行得非常好。优雅的解决方案加一分。 - saintjab
3
抱歉,但这个方法是切换开关式的,所以如果键盘状态已经关闭,它会重新显示键盘。 - HendraWD
not a solution here - famfamfam

20

使用更为流行的 KotlinMaterial Design,可以通过使用TextInputEditText来实现(这种方法也适用于EditTextView)...

1. 使父视图(你的活动/片段的内容视图)可点击和可聚焦,通过添加以下属性:

android:focusable="true"
android:focusableInTouchMode="true"
android:clickable="true"

2.为所有视图创建一个扩展(例如在ViewExtension.kt文件中):

fun View.hideKeyboard(){
    val inputMethodManager = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
    inputMethodManager.hideSoftInputFromWindow(this.windowToken, 0)
}

3.创建一个继承TextInputEditText的BaseTextInputEditText。实现onFocusChanged方法,使当视图失去焦点时隐藏键盘:

class BaseTextInputEditText(context: Context?, attrs: AttributeSet?) : TextInputEditText(context, attrs){
    override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect)
        if (!focused) this.hideKeyboard()
    }
}

4.只需在XML中调用您全新的自定义视图:

<android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayout"
        ...>

        <com.your_package.BaseTextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            ... />

    </android.support.design.widget.TextInputLayout> 

就这样。 无需修改您的控制器(片段或活动)来处理这种重复情况。


17

我设法解决了这个问题,我覆盖了我的activity上的dispatchTouchEvent,使用以下代码隐藏键盘。

 /**
 * Called to process touch screen events. 
 */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            touchDownTime = SystemClock.elapsedRealtime();
            break;

        case MotionEvent.ACTION_UP:
            //to avoid drag events
            if (SystemClock.elapsedRealtime() - touchDownTime <= 150){  

                EditText[] textFields = this.getFields();
                if(textFields != null && textFields.length > 0){

                    boolean clickIsOutsideEditTexts = true;

                    for(EditText field : textFields){
                        if(isPointInsideView(ev.getRawX(), ev.getRawY(), field)){
                            clickIsOutsideEditTexts = false;
                            break;
                        }
                    }

                    if(clickIsOutsideEditTexts){
                        this.hideSoftKeyboard();
                    }               
                } else {
                    this.hideSoftKeyboard();
                }
            }
            break;
    }

    return super.dispatchTouchEvent(ev);
}

编辑: getFields() 方法只是返回视图中的文本字段数组的方法。 为了避免在每次触摸时创建此数组,我创建了一个名为sFields的静态数组,该数组在getFields()方法中返回。 这个数组在onStart()方法中进行初始化,例如:

sFields = new EditText[] {mUserField, mPasswordField};

它并不完美,拖动事件时间仅基于启发式算法,因此有时在执行长按操作时不会隐藏,我还创建了一个获取所有EditText的方法以避免在单击其他EditText时键盘隐藏和显示。

尽管如此,我们仍然欢迎更干净简短的解决方案。


8
为了帮助未来的他人,您是否考虑编辑您回答中的代码,包括您的 getFields() 方法?它不必完全相同,只需要一个示例,可能只需一些注释来指示它返回一个 EditText 对象数组即可。 - Squonk
假设我们在Activity中保存代码并从那里访问EditText。目前这是一种过时的解决方案。请参见类似的JCutting8答案。 - CoolMind

15

使用OnFocusChangeListener

例如:

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

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


11
问题在于当我点击一个标签或其他不可聚焦的视图时,EditText没有失去焦点。 - htafoya
在这种情况下,我还有一个解决方案。我已经更新了答案。 - Sergey Glotov
2
onTouchEvent 被调用太多次,这也不是一个好的做法。 - Jesus Dimrix

15

在任何活动中覆盖公共布尔值dispatchTouchEvent(MotionEvent event)方法(或扩展Activity类的类)

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    View view = getCurrentFocus();
    boolean ret = super.dispatchTouchEvent(event);

    if (view instanceof EditText) {
        View w = getCurrentFocus();
        int scrcoords[] = new int[2];
        w.getLocationOnScreen(scrcoords);
        float x = event.getRawX() + w.getLeft() - scrcoords[0];
        float y = event.getRawY() + w.getTop() - scrcoords[1];
        
        if (event.getAction() == MotionEvent.ACTION_UP 
 && (x < w.getLeft() || x >= w.getRight() 
 || y < w.getTop() || y > w.getBottom()) ) { 
            InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(getWindow().getCurrentFocus().getWindowToken(), 0);
        }
    }
 return ret;
}

Kotlin版本:

 override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        val ret = super.dispatchTouchEvent(ev)
        ev?.let { event ->
            if (event.action == MotionEvent.ACTION_UP) {
                currentFocus?.let { view ->
                    if (view is EditText) {
                        val touchCoordinates = IntArray(2)
                        view.getLocationOnScreen(touchCoordinates)
                        val x: Float = event.rawX + view.getLeft() - touchCoordinates[0]
                        val y: Float = event.rawY + view.getTop() - touchCoordinates[1]
                        //If the touch position is outside the EditText then we hide the keyboard
                        if (x < view.getLeft() || x >= view.getRight() || y < view.getTop() || y > view.getBottom()) {
                            val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
                            imm.hideSoftInputFromWindow(view.windowToken, 0)
                            view.clearFocus()
                        }
                    }
                }
            }
        }
        return ret
    }

这就是你需要做的全部


这是我发现的最简单的使其工作的方法。可以与多个EditText和ScrollView一起使用。 - RJH
已经使用多个EditText进行了测试,它可以正常工作!唯一的缺点是当您进行拖动操作时,它也会隐藏。 - RominaV
它的功能很好,但在ScrollView滚动期间,它也会隐藏键盘。 - CoolMind

14

我在Activity中实现了dispatchTouchEvent来完成这个功能:

private EditText mEditText;
private Rect mRect = new Rect();
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = MotionEventCompat.getActionMasked(ev);

    int[] location = new int[2];
    mEditText.getLocationOnScreen(location);
    mRect.left = location[0];
    mRect.top = location[1];
    mRect.right = location[0] + mEditText.getWidth();
    mRect.bottom = location[1] + mEditText.getHeight();

    int x = (int) ev.getX();
    int y = (int) ev.getY();

    if (action == MotionEvent.ACTION_DOWN && !mRect.contains(x, y)) {
        InputMethodManager input = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        input.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
    }
    return super.dispatchTouchEvent(ev);
}

我测试过了,完美运作!


可以工作,但问题在于如果我们有多个EditText,那么我们也需要考虑到它们,不过我喜欢你的答案 :-) - Lalit Poptani
getActionMasked(ev)已被弃用,现在请使用:final int action = ev.getActionMasked();作为第一行。 - Andrew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接