Detecting when user has dismissed the soft keyboard

Viewed 83656

I have an EditText widget in my view. When the user selects the EditText widget, I display some instructions and the soft keyboard appears.

I use an OnEditorActionListener to detect when the user has completed text entry and I dismiss the keyboard, hide the instructions and perform some action.

My problem is when the user dismisses the keyboard by pressing the BACK key. The OS dismisses the keyboard, but my instructions (which I need to hide) are still visible.

I've tried overriding OnKeyDown, but that doesn't seem to get called when the BACK button is used to dismiss the keyboard.

I've tried setting an OnKeyListener on the EditText widget, but that doesn't seem to get called either.

How can I detect when the soft keyboard is being dismissed?

11 Answers

It's 2019 now...
So I created a more neat solution with Kotlin

1.Create an extension function:

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.Where the toggle listener is:

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.Use it in any Activity as simple as this:

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

Using @olivier_sdg's answer, but converted to 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)
    }

}

Usage:

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

For anyone looking to do the same in Xamarin, I've translated some of the top answers as it's a bit different. I created a gist here but summarizing, you create a custom EditText and override OnKeyPreIme like so:

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);
    }
}

... and then in the view...

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

hideSoftInputFromWindow returns true when keyboard closes use it's value to detect keyboard close in android

InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);

        if (imm.hideSoftInputFromWindow(findFocus().getWindowToken(),
                InputMethodManager.HIDE_NOT_ALWAYS)) {
            //keyboard is closed now do what you need here
        }

Koltin solution which works in landscape mode as well (onKeyPreIme was not being called if user press Done button in landscape mode):

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

Related