从软键盘拦截后退按钮

我有几个输入字段的活动。当活动开始时,显示软键盘。当后退按钮按下软键盘关闭和关闭活动,我需要按下后退按钮一次。

因此,问题是: 是否可能拦截后退按钮关闭软键盘和完成活动在一个按下后退按钮没有创建自定义 InputMethodService

另外,我知道如何在其他情况下拦截返回按钮: onKeyDown()onBackPressed(),但它在这种情况下不工作: 只有第二次按下返回按钮被拦截。

94966 次浏览

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

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

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

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.

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

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

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.

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

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

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