Intercept back button from soft keyboard
Asked Answered
F

10

90

I have the activity with several input fields. When activity started soft keyboard is shown. When back button pressed soft keyboard closes and to close activity I need to press back button one more time.

So the question: is it possible to intercept back button to close soft keyboard and finish activity in one press of back button without creating custom InputMethodService?

P.S. I know how to intercept back button in other cases: onKeyDown() or onBackPressed() but it doesn't work in this case: only second press of back button is intercepted.

Frenzy answered 15/10, 2010 at 6:59 Comment(0)
D
78

Yes, it is completely possible to show and hide the keyboard and intercept the calls to the back button. It is a little extra effort as it has been mentioned there is no direct way to do this in the API. The key is to override boolean dispatchKeyEventPreIme(KeyEvent) within a layout. What we do is create our layout. I chose RelativeLayout since it was the base of my Activity.

<?xml version="1.0" encoding="utf-8"?>
<com.michaelhradek.superapp.utilities.SearchLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.michaelhradek.superapp"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/white">

Inside our Activity we set up our input fields and call the setActivity(...) function.

private void initInputField() {
    mInputField = (EditText) findViewById(R.id.searchInput);        

    InputMethodManager imm = 
        (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 
    imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 
            InputMethodManager.HIDE_IMPLICIT_ONLY);

    mInputField.setOnEditorActionListener(new OnEditorActionListener() {

        @Override
        public boolean onEditorAction(TextView v, int actionId,
                KeyEvent event) {
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                performSearch();
                return true;
            }

            return false;
        }
    });

    // Let the layout know we are going to be overriding the back button
    SearchLayout.setSearchActivity(this);
}

Obviously, the initInputField() function sets up the input field. It also enables the enter key to execute the functionality (in my case a search).

@Override
public void onBackPressed() {
    // It's expensive, if running turn it off.
    DataHelper.cancelSearch();
    hideKeyboard();
    super.onBackPressed();
}

So when the onBackPressed() is called within our layout we then can do whatever we want like hide the keyboard:

private void hideKeyboard() {
    InputMethodManager imm = (InputMethodManager) 
        getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(mInputField.getWindowToken(), 0);
}

Anyway, here is my override of the RelativeLayout.

package com.michaelhradek.superapp.utilities;

import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.RelativeLayout;

/**
 * The root element in the search bar layout. This is a custom view just to 
 * override the handling of the back button.
 * 
 */
public class SearchLayout extends RelativeLayout {

    private static final String TAG = "SearchLayout";

    private static Activity mSearchActivity;;

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

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

    public static void setSearchActivity(Activity searchActivity) {
        mSearchActivity = searchActivity;
    }

    /**
     * Overrides the handling of the back key to move back to the 
     * previous sources or dismiss the search dialog, instead of 
     * dismissing the input method.
     */
    @Override
    public boolean dispatchKeyEventPreIme(KeyEvent event) {
        Log.d(TAG, "dispatchKeyEventPreIme(" + event + ")");
        if (mSearchActivity != null && 
                    event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
            KeyEvent.DispatcherState state = getKeyDispatcherState();
            if (state != null) {
                if (event.getAction() == KeyEvent.ACTION_DOWN
                        && event.getRepeatCount() == 0) {
                    state.startTracking(event, this);
                    return true;
                } else if (event.getAction() == KeyEvent.ACTION_UP
                        && !event.isCanceled() && state.isTracking(event)) {
                    mSearchActivity.onBackPressed();
                    return true;
                }
            }
        }

        return super.dispatchKeyEventPreIme(event);
    }
}

Unfortunately I can't take all the credit. If you check the Android source for the quick SearchDialog box you will see where the idea came from.

Davies answered 27/4, 2011 at 22:57 Comment(6)
This worked very well, and wasn't too hard to implement (despite looking a little scary).Dibbrun
Thank you very much for this, I just wish I had a way of getting something like that to work with older versions of Android.Athallia
Code can be simplified by just casting getContext() to Activity. Provided the layout's context is the activity in question of course. But I don't know if it couldn't be.Pewter
static mSearchActivity - that smells wrong by a problem. What if you have two of these in layout? Use non-static variable instead.Clothesline
Works like a charm. But, I think it is better to use local static interface and set it implementation from inside activity instead of using static acticty...Bra
Works for me on Android 6, 7 and 8Bainbridge
C
86

onKeyDown() and onBackPressed() doesn't work for this case. You have to use onKeyPreIme.

Initially, you have to create custom edit text that extends EditText. And then you have to implement onKeyPreIme method which controls KeyEvent.KEYCODE_BACK. After this, one back press enough for solve your problem. This solution works for me perfectly.

CustomEditText.java

public class CustomEditText extends EditText {

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

    @Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            // User has pressed Back key. So hide the keyboard
            InputMethodManager mgr = (InputMethodManager)         

           getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            mgr.hideSoftInputFromWindow(this.getWindowToken(), 0);
            // TODO: Hide your view as you do it in your activity
        }
        return false;
}

In your XML

<com.YOURAPP.CustomEditText
     android:id="@+id/CEditText"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"/> 

In your Activity

public class MainActivity extends Activity {
   private CustomEditText editText;

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      editText = (CustomEditText) findViewById(R.id.CEditText);
   }
}
Cinerarium answered 25/2, 2015 at 12:46 Comment(10)
Love this answer so much. Using this with a fragment as well (within a search bar which doesn't even show up always), and by suggesting to override the editText, this easily encapsulates this behavior to only when its necessaryPracticable
Note to everybody: Adding a quick interface to this makes life so much better, and it's fortunately pretty easy to doPracticable
how to call a method in onKeyPreIme() which is in mainActivity.java ?Acetal
You don't need to call onKeyPreIme in activity. If you use CustomEditText in your activity, method is triggered when you press any button on keyboard.Cinerarium
in TODO area, return true so the call does not propagate to main activity.Stav
this is way better than the answer above imoDeva
I think this is the best answer, because it's much more simpler to implement. in fact you just have to copy the class "CustomEditText" (and rename your edittext in XML's) and make what you want in the // TODO area. And just add "return true" in TODO area if you do not want to close your activity. Thanks !Supereminent
this was the best answer! been looking for a while... @Acetal you implement a listener in the custom edit text which you then add to the main activityPecoraro
just testing in Pixel 4XL device and Nexus emulator, both onKeyPreIme and dispatchKeyEvent doesn't fired with soft keyboard (only fired with hard keyboard <- type from computer)Joub
best answer. simple but practicalBo
D
78

Yes, it is completely possible to show and hide the keyboard and intercept the calls to the back button. It is a little extra effort as it has been mentioned there is no direct way to do this in the API. The key is to override boolean dispatchKeyEventPreIme(KeyEvent) within a layout. What we do is create our layout. I chose RelativeLayout since it was the base of my Activity.

<?xml version="1.0" encoding="utf-8"?>
<com.michaelhradek.superapp.utilities.SearchLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.michaelhradek.superapp"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/white">

Inside our Activity we set up our input fields and call the setActivity(...) function.

private void initInputField() {
    mInputField = (EditText) findViewById(R.id.searchInput);        

    InputMethodManager imm = 
        (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); 
    imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 
            InputMethodManager.HIDE_IMPLICIT_ONLY);

    mInputField.setOnEditorActionListener(new OnEditorActionListener() {

        @Override
        public boolean onEditorAction(TextView v, int actionId,
                KeyEvent event) {
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                performSearch();
                return true;
            }

            return false;
        }
    });

    // Let the layout know we are going to be overriding the back button
    SearchLayout.setSearchActivity(this);
}

Obviously, the initInputField() function sets up the input field. It also enables the enter key to execute the functionality (in my case a search).

@Override
public void onBackPressed() {
    // It's expensive, if running turn it off.
    DataHelper.cancelSearch();
    hideKeyboard();
    super.onBackPressed();
}

So when the onBackPressed() is called within our layout we then can do whatever we want like hide the keyboard:

private void hideKeyboard() {
    InputMethodManager imm = (InputMethodManager) 
        getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(mInputField.getWindowToken(), 0);
}

Anyway, here is my override of the RelativeLayout.

package com.michaelhradek.superapp.utilities;

import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.RelativeLayout;

/**
 * The root element in the search bar layout. This is a custom view just to 
 * override the handling of the back button.
 * 
 */
public class SearchLayout extends RelativeLayout {

    private static final String TAG = "SearchLayout";

    private static Activity mSearchActivity;;

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

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

    public static void setSearchActivity(Activity searchActivity) {
        mSearchActivity = searchActivity;
    }

    /**
     * Overrides the handling of the back key to move back to the 
     * previous sources or dismiss the search dialog, instead of 
     * dismissing the input method.
     */
    @Override
    public boolean dispatchKeyEventPreIme(KeyEvent event) {
        Log.d(TAG, "dispatchKeyEventPreIme(" + event + ")");
        if (mSearchActivity != null && 
                    event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
            KeyEvent.DispatcherState state = getKeyDispatcherState();
            if (state != null) {
                if (event.getAction() == KeyEvent.ACTION_DOWN
                        && event.getRepeatCount() == 0) {
                    state.startTracking(event, this);
                    return true;
                } else if (event.getAction() == KeyEvent.ACTION_UP
                        && !event.isCanceled() && state.isTracking(event)) {
                    mSearchActivity.onBackPressed();
                    return true;
                }
            }
        }

        return super.dispatchKeyEventPreIme(event);
    }
}

Unfortunately I can't take all the credit. If you check the Android source for the quick SearchDialog box you will see where the idea came from.

Davies answered 27/4, 2011 at 22:57 Comment(6)
This worked very well, and wasn't too hard to implement (despite looking a little scary).Dibbrun
Thank you very much for this, I just wish I had a way of getting something like that to work with older versions of Android.Athallia
Code can be simplified by just casting getContext() to Activity. Provided the layout's context is the activity in question of course. But I don't know if it couldn't be.Pewter
static mSearchActivity - that smells wrong by a problem. What if you have two of these in layout? Use non-static variable instead.Clothesline
Works like a charm. But, I think it is better to use local static interface and set it implementation from inside activity instead of using static acticty...Bra
Works for me on Android 6, 7 and 8Bainbridge
H
13

I found out, that overriding the dispatchKeyEventPreIme method of the Layout Class also works well. Just set your main Activity as an attribute and launch a predefined method.

public class LinearLayoutGradient extends LinearLayout {
    MainActivity a;

    public void setMainActivity(MainActivity a) {
        this.a = a;
    }

    @Override
    public boolean dispatchKeyEventPreIme(KeyEvent event) {
        if (a != null) {
            InputMethodManager imm = (InputMethodManager) a
                .getSystemService(Context.INPUT_METHOD_SERVICE);

            if (imm.isActive() && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
                a.launchMethod;
            }
        }

        return super.dispatchKeyEventPreIme(event);
    }
}
Hod answered 5/6, 2011 at 18:14 Comment(4)
can you show me your xml for this layout? i am trying to check this approach and i have surface view in my custum layout, but it isnt intercepting anythingKolva
Basicly the custom view is the surrounding element in the xml. <?xml version="1.0" encoding="utf-8"?> <view class="package.LinearLayoutGradient"...Hod
it also works if we simply extend a single view and override it's dispatchKeyEventPreIme(). It is not required to extend the layout itself.Euripus
This approach worked well for me. I extended the layout I was interested in and then passed in a call back interface rather than a reference to the activity.Magness
M
7

I had success by overriding dispatchKeyEvent:

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
        finish();
        return true;
    }
    return super.dispatchKeyEvent(event);
}

It hides the keyboard and finishes the activity.

Maenad answered 24/2, 2012 at 0:29 Comment(4)
this appears to work only on certain devices such as HTC Desire.Redding
does not work on samsung note 2. The dispatchKeyEvent() is not called when soft keyboard is up.Euripus
Well, I tried this with the Nexus 6 Emulator (Lollipop 5.0) -> worked and Note 4 (CM12 Lollipop 5.0.2) -> worked not so, not really the best solution - but anyway.. Thank you :)Discourteous
Button click is consumed when dismissing the keyboardHuan
U
4

How are you showing the soft keyboard?

If you are using InputMethodManager.showSoftInput(), you can try passing in a ResultReceiver and implementing onReceiveResult() to handle RESULT_HIDDEN

http://developer.android.com/reference/android/view/inputmethod/InputMethodManager.html

Ursel answered 15/10, 2010 at 7:16 Comment(2)
Sounds good, but showSoftInput() doesn't show keyboard at my phone and emulator, so I use toggleSoftInput()Frenzy
Doesn't work for me. onReceiveResult() is called with RESULT_SHOWN, but never later with RESULT_HIDDENCrybaby
T
2

I had the same problem but got around it by intercepting the back key press. In my case (HTC Desire, Android 2.2, Application API Level 4) it closes the Keyboard and immediately finishes the Activity. Don't know why this should not work for you too:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        return true;
    }
    return super.onKeyDown(keyCode, event);
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        onBackPressed();
        return true;
    }
    return super.onKeyUp(keyCode, event);
}

/**
 * Called when the activity has detected the user's press of the back key
 */
private void onBackPressed() {
    Log.e(TAG, "back pressed");
    finish();
}
Thomasina answered 3/3, 2011 at 11:1 Comment(1)
this does not work on samsung galaxy note 2. the onkeydown() or onbackpressed() are not triggered when the soft keyboard is visible and the back button is pressedEuripus
S
1

Use onKeyPreIme(int keyCode, KeyEvent event) method and check for KeyEvent.KEYCODE_BACK event. It's very simple without doing any fancy coding.

Sauerkraut answered 21/11, 2011 at 16:9 Comment(0)
F
0

Try this code in your BackPressed implementation(Block Back Button in android):

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

I suggest you to have a look @ Close/hide the Android Soft Keyboard

Floruit answered 22/10, 2010 at 6:40 Comment(2)
I've already tried this, but it still need two presses on back button. As I understand it the first click is intercepted by soft keyboard so onBackPressed() doesn't work. And only after the second press program falls into the onBackPressed().Frenzy
@Sergey Glotov I tried lots of suggestions for the problem & finally came with a not so good solution where I have to implement own SoftKeyBoard.But I hope android community will soon come out with a much better solution.Floruit
J
0

my version of @mhradek solution:

Layout

class BazingaLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
    ConstraintLayout(context, attrs, defStyleAttr) {

var activity: Activity? = null

override fun dispatchKeyEventPreIme(event: KeyEvent): Boolean {
    activity?.let {
        if (event.keyCode == KeyEvent.KEYCODE_BACK) {
            val state = keyDispatcherState
            if (state != null) {
                if (event.action == KeyEvent.ACTION_DOWN
                    && event.repeatCount == 0) {
                    state.startTracking(event, this)
                    return true
                } else if (event.action == KeyEvent.ACTION_UP && !event.isCanceled && state.isTracking(event)) {
                    it.onBackPressed()
                    return true
                }
            }
        }
    }
    return super.dispatchKeyEventPreIme(event)
}

}

xml file

<com... BazingaLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/grey">
 </com... BazingaLayout>

Fragment

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        (view as BazingaLayout).activity = activity
        super.onViewCreated(view, savedInstanceState)
}
Junction answered 20/3, 2020 at 19:33 Comment(0)
M
0

Here's my variation of @kirill-rakhman's solution.

I needed to know when the hardware or gesture back button was pressed while the keyboard was showing so I could react and show a button that had previously been hidden when any of the edit text views had received focus.

  1. First declare the call back interface your require
interface KeyboardEventListener {
   fun onKeyBoardDismissedIme()
}
  1. Then create the custom view with the listener for pre ime key events
class KeyboardAwareConstraintLayout(context: Context, attrs: AttributeSet) :
    ConstraintLayout(context, attrs) {

    var listener: KeyboardEventListener? = null

    override fun dispatchKeyEventPreIme(event: KeyEvent?): Boolean {
        val imm: InputMethodManager =
            context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        if (imm.isActive && event?.keyCode == KeyEvent.KEYCODE_BACK) {
            listener?.onKeyBoardDismissedIme()
        }
        return super.dispatchKeyEventPreIme(event)
    }
}
  1. Wrap your layout with the custom layout view
<com.package_name.KeyboardAwareLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/keyBoardAwareLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

{Your layout children here}    

</com.package_name.KeyboardAwareLayout>

  1. Next implement the callback interface in your activity or fragment and set on the keyboard aware layout
class MyFragment : Fragment, KeyboardEventListener {

    // TODO: Setup some viewbinding to retrieve the view reference

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.keyBoardAwareLayout.listener = this
    }

    override fun onKeyBoardDismissedIme() {
        // TODO: React to keyboard hidden with back button
    }
}

Magness answered 29/4, 2021 at 15:11 Comment(1)
Any idea why this may not work in API 33?Mahan

© 2022 - 2024 — McMap. All rights reserved.