Detecting when user has dismissed the soft keyboard
Asked Answered
I

11

123

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?

Inandin answered 6/8, 2010 at 16:50 Comment(0)
A
165

I know a way to do this. Subclass the EditText and implement:

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

Here is a link on how to use your custom views (for when you subclass EditText): http://developer.android.com/guide/topics/ui/custom-components.html

Alcalde answered 8/12, 2010 at 17:46 Comment(10)
I am getting reports from Android users with Hardware keyboards that doing this somehow interferes with key presses. I don't have any additional info at this time.Nomism
I've been looking on several solution, this is by far the best!Spoilfive
Wait wait wait, I just looked at this a third time - shouldn't the super call be to onKeyPreIme? Or is there a particular reason for it not to be so?Hildebrand
Looks useful, except where the EditText can't be subclassed (e.g. in a SearchView). This is a problem when trying to hide the SearchView if empty when the keyboard is dismissed. I have to wonder why the android folks don't just provide some nice OSK APIs for this sort of thing.Wheaten
@Wheaten To achieve similar effect in SearchView, please refer to #9629813Eccentric
There are other ways to hide keyboard other than BACK, like the buttons in the Keyboard or click some other places. So this method works in some cases. Currently, there is no thorough solution for this question.Sorghum
Is there a way to detect when keyboard opens?Facetiae
You should extends AppCompatEditText instead of EditText to benefit chris banes's code (in particular for TintableBackgroundView and more)Achromaticity
on new devices dispatchKeyEvent() not called when you click back button while keyboard is opened. Therefore not called onKeyPreIme()Tia
did not work for wearable back swipe on IME keyboard :( any solution for suchPostmortem
S
130

Jay, your solution is good ! thanks :)

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);
}
Schnur answered 13/5, 2011 at 14:13 Comment(5)
Any particular reason that we want to detect KeyEvent.ACTION_UP as well?Eccentric
@CheokYanCheng it is because user action should normally take effect when releasing button, and not when starting to press it.Selma
Make sure to extend android.support.v7.widget.AppCompatEditText for tinting.Sonde
Extend: AppCompatEditText for androidxRenal
Great! I'd suggest only an improvement just to generify your solution. I would pass the arguments from onKeyPreIme "as is" to the listener, on this way you can implement your logic in different ways where you need.Monkery
P
19

I made a slight change on Jay's solution by calling 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);
    }
};
Politic answered 30/4, 2012 at 10:14 Comment(1)
Working as expected.Consultation
Y
15

Here is my custom EditText to detect whether keyboard is showing or not

/**
 * 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);
    }
}
Yvetteyvon answered 24/9, 2015 at 5:40 Comment(0)
R
11

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!!
      }
Rosinarosinante answered 19/2, 2019 at 7:48 Comment(3)
Thanks for the Kotlin solution. Though when I implemented it I noticed that it fires the listener multiple times for one keyboard change, and also on startup. I had to store the open/not open state and only invoke listeners when the value was actually different.Dampen
@Dampen fixed it in KeyboardToggleListener. Thanks for noticingRosinarosinante
For fragments don't forget to de-register it.Gabrielgabriela
R
10

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
    }
}
Rutledge answered 24/1, 2018 at 18:41 Comment(1)
I hate to admit, but this is the most direct "dumb, but works" way.Ptyalin
C
5

Just create a class that extends Edittext and use that edittext in your code, you should just override the following method in the custom 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;}
Continuant answered 7/6, 2015 at 7:31 Comment(1)
Is there a way to detect when keyboard opens?Facetiae
G
3

Here's a solution with the key listener. I have no idea why this works but OnKeyListener works if you just purely override onKeyPreIme on your custom EditText.

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);
    }
Garaway answered 17/1, 2017 at 5:30 Comment(0)
C
2

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>
};
Cementum answered 10/6, 2018 at 19:35 Comment(1)
While it's just a simple example, I would recommend not using anonymous methods in event handlers, as these create memory leaks, and many people use the examples found here and run with them without realizing this. Source: learn.microsoft.com/en-us/xamarin/cross-platform/deploy-test/…Nape
G
0

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
        }
Griz answered 26/8, 2019 at 15:53 Comment(0)
P
0

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

Posthorse answered 23/12, 2021 at 9:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.