检测用户何时关闭了软键盘

123

我在视图中有一个EditText小部件。当用户选择EditText小部件时,我会显示一些说明并弹出软键盘。

我使用OnEditorActionListener来检测用户何时完成文本输入,并关闭键盘、隐藏说明并执行某些操作。

我的问题是当用户按BACK键关闭键盘时,操作系统会关闭键盘,但我的说明(我需要隐藏)仍然可见。

我尝试重写OnKeyDown方法,但BACK按钮用于关闭键盘时似乎不会调用该方法。

我尝试在EditText小部件上设置OnKeyListener,但也似乎不会调用该方法。

如何检测软键盘被关闭?

11个回答

165

我知道一种方法。子类化EditText并实现:

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
  if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
    // Do your thing.
    return true;  // So it is not propagated.
  }
  return super.dispatchKeyEvent(event);
}

这是一个关于如何使用自定义视图的链接(用于当您子类化EditText时):http://developer.android.com/guide/topics/ui/custom-components.html


3
我收到来自使用带有硬件键盘的 Android 用户的反馈,称这样做会干扰按键操作。目前我没有其他信息。 - esilver
1
我已经寻找了几种解决方案,这绝对是最好的! - Friesgaard
13
等等等,我第三次看这个代码,难道super调用不应该是onKeyPreIme吗?还是有特殊原因不这样做? - Erhannis
2
@tbm 要在 SearchView 中实现类似的效果,请参考 https://dev59.com/BWkw5IYBdhLWcg3wzd3Z - Cheok Yan Cheng
1
除了BACK键之外,还有其他隐藏键盘的方法,例如键盘上的按钮或单击其他位置。因此,该方法在某些情况下有效。目前,对于这个问题还没有彻底的解决方案。 - alexhilton
显示剩余5条评论

130

Jay,你的解决方案很棒!谢谢 :)

public class EditTextBackEvent extends EditText {

    private EditTextImeBackListener mOnImeBack;

    public EditTextBackEvent(Context context) {
        super(context);
    }

    public EditTextBackEvent(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EditTextBackEvent(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && 
            event.getAction() == KeyEvent.ACTION_UP) {
            if (mOnImeBack != null) 
                mOnImeBack.onImeBack(this, this.getText().toString());
        }
        return super.dispatchKeyEvent(event);
    }

    public void setOnEditTextImeBackListener(EditTextImeBackListener listener) {
        mOnImeBack = listener;
    }

}

public interface EditTextImeBackListener {
    public abstract void onImeBack(EditTextBackEvent ctrl, String text);
}

1
我们为什么要检测 KeyEvent.ACTION_UP?有特别的原因吗? - Cheok Yan Cheng
4
这是因为用户的操作通常应在释放按钮时生效,而不是在开始按下按钮时生效。 - jayeffkay
3
请确保扩展android.support.v7.widget.AppCompatEditText以进行着色。 - user5055032
扩展:AppCompatEditText 用于 androidx。 - COYG
太好了!我建议只需改进一下,将您的解决方案泛化。我会将参数从onKeyPreIme“原样”传递给监听器,这样您就可以在需要时以不同的方式实现自己的逻辑。 - marcRDZ

19

我对Jay的解决方案进行了一点修改,通过调用super.onKeyPreIme():

_e = new EditText(inflater.getContext()) {
    @Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK){
            cancelTextInput();
        }
        return super.onKeyPreIme(keyCode, event);
    }
};

正常工作。 - Derek K

15

这是我的自定义EditText,用于检测键盘是否显示

/**
 * Created by TheFinestArtist on 9/24/15.
 */
public class KeyboardEditText extends EditText {

    public KeyboardEditText(Context context) {
        super(context);
    }

    public KeyboardEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public KeyboardEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect);
        if (listener != null)
            listener.onStateChanged(this, true);
    }

    @Override
    public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
                && event.getAction() == KeyEvent.ACTION_UP) {
            if (listener != null)
                listener.onStateChanged(this, false);
        }
        return super.onKeyPreIme(keyCode, event);
    }

    /**
     * Keyboard Listener
     */
    KeyboardListener listener;

    public void setOnKeyboardListener(KeyboardListener listener) {
        this.listener = listener;
    }

    public interface KeyboardListener {
        void onStateChanged(KeyboardEditText keyboardEditText, boolean showing);
    }
}

11

现在是2019年...
所以我用Kotlin创建了一个更简洁的解决方案

1.创建一个扩展函数:

fun Activity.addKeyboardToggleListener(onKeyboardToggleAction: (shown: Boolean) -> Unit): KeyboardToggleListener? {
    val root = findViewById<View>(android.R.id.content)
    val listener = KeyboardToggleListener(root, onKeyboardToggleAction)
    return root?.viewTreeObserver?.run {
        addOnGlobalLayoutListener(listener)
        listener
    }
}

2. 切换监听器所在的位置:

open class KeyboardToggleListener(
        private val root: View?,
        private val onKeyboardToggleAction: (shown: Boolean) -> Unit
) : ViewTreeObserver.OnGlobalLayoutListener {
    private var shown = false
    override fun onGlobalLayout() {
        root?.run {
            val heightDiff = rootView.height - height
            val keyboardShown = heightDiff > dpToPx(200f)
            if (shown != keyboardShown) {
                onKeyboardToggleAction.invoke(keyboardShown)
                shown = keyboardShown
            }
        }
    }
}

fun View.dpToPx(dp: Float) = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics).roundToInt()

3. 只需这样简单地在任何活动中使用它

addKeyboardToggleListener {shown ->
          // hurray! Now you know when the keyboard is shown and hidden!!
      }

感谢 Kotlin 的解决方案。然而,当我实现它时,我注意到它会在一个键盘更改时多次触发监听器,并且在启动时也是如此。我不得不存储打开/关闭状态,并且仅在值实际上不同的情况下调用监听器。 - RandomEngy
@RandomEngy 在 KeyboardToggleListener 中修复了它。感谢您的注意。 - Leo DroidCoder
1
对于片段,请不要忘记取消注册。 - Cosmic Dev

10

使用 @olivier_sdg 的答案,但是将其转换为 Kotlin 语言:

class KeyboardEditText : AppCompatEditText {

    var listener: Listener? = null
  
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
  
    override fun onKeyPreIme(keyCode: Int, event: KeyEvent): Boolean {
        if (event.keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
            listener?.onImeBack(this)
        }
        return super.dispatchKeyEvent(event)
    }
  
    interface Listener {
        fun onImeBack(editText: KeyboardEditText)
    }

}

用法:

keyboardEditText.listener = object : KeyboardEditText.Listener {
    override fun onImeBack(editText: KeyboardEditText) {
        //Back detected
    }
}

我不得不承认,这是最直接的“愚蠢但有效”的方法。 - Morgan Koh

5

只需创建一个扩展Edittext的类,并在代码中使用该edittext,您应该在自定义edittext中覆盖以下方法:

@Override
 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
 if (keyCode == KeyEvent.KEYCODE_BACK) {

    //Here it catch all back keys
    //Now you can do what you want.

} else if (keyCode == KeyEvent.KEYCODE_MENU) {
    // Eat the event
    return true;
}
return false;}

有没有办法检测键盘何时打开? - powder366

3

以下是使用按键监听器的解决方案。 我不知道为什么这个方法可行,但如果只是在自定义的EditText上纯粹覆盖onKeyPreIme,OnKeyListener就能起作用。

SomeClass.java

customEditText.setOnKeyListener((v, keyCode, event) -> {
            if(event.getAction() == KeyEvent.ACTION_DOWN) {
                switch (keyCode) {
                    case KeyEvent.KEYCODE_BACK:
                        getPresenter().onBackPressed();
                        break;
                }
            }
            return false;
        }); 

CustomEditText.java

@Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        return super.dispatchKeyEvent(event);
    }

2

对于想在Xamarin中实现同样功能的人,我已将一些最受欢迎的答案翻译出来,因为这有点不同。 我创建了一个gist (此处) 但总结一下,您需要创建一个自定义EditText并覆盖 OnKeyPreIme 方法,就像这样:

public class CustomEditText : EditText
{
    public event EventHandler BackPressed;

    // ...

    public override bool OnKeyPreIme([GeneratedEnum] Keycode keyCode, KeyEvent e)
    {
        if (e.KeyCode == Keycode.Back && e.Action == KeyEventActions.Up)
        {
            BackPressed?.Invoke(this, new EventArgs());
        }

        return base.OnKeyPreIme(keyCode, e);
    }
}

...然后在视图中...

editText = FindViewById<CustomEditText>(Resource.Id.MyEditText);
editText.BackPressed += (s, e) => 
{
    // <insert code here>
};

虽然这只是一个简单的例子,但建议不要在事件处理程序中使用匿名方法,因为它们会导致内存泄漏,许多人使用在此处找到的示例并运行而不意识到这一点。 - Jared

0

Kotlin 解决方案,可以在横屏模式下正常工作(如果用户在横屏模式下按下 Done 按钮,则不会调用 onKeyPreIme):

class CustomEditText(context: Context, attrs: AttributeSet) : AppCompatEditText(context, attrs) {

    override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
        if ((event?.keyCode == KeyEvent.KEYCODE_ENTER) && (event?.action == KeyEvent.ACTION_UP)) {
            if (hasFocus())
                clearFocus()
        }
        return super.onKeyUp(keyCode, event)
    }

    override fun onKeyPreIme(keyCode: Int, event: KeyEvent?): Boolean {
        if ((event?.keyCode == KeyEvent.KEYCODE_BACK) && (event?.action == KeyEvent.ACTION_UP)) {
            if (hasFocus())
                clearFocus()
        }
        return super.onKeyPreIme(keyCode, event)
    }
}

enter image description here


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