拦截软键盘的返回按钮

90

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

因此问题是:是否可以拦截返回按钮以关闭软键盘并在按下返回按钮时完成活动,而不创建自定义 InputMethodService

P.S. 我知道如何在其他情况下拦截返回按钮:onKeyDown()onBackPressed(),但在这种情况下无效:只有第二次按下返回按钮才被拦截。

10个回答

86

onKeyDown()onBackPressed()在这种情况下无法正常工作。您需要使用onKeyPreIme

最初,您需要创建扩展EditText的自定义EditText。然后,您必须实现控制KeyEvent.KEYCODE_BACK的onKeyPreIme方法。之后,一次返回足以解决您的问题。这个解决方案对我来说完美地运行。

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

在你的XML中

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

在您的活动中

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

4
非常喜欢这个答案。还可与片段一起使用(在搜索栏内,有时甚至不会显示)。通过建议覆盖编辑文本框,可以轻松地将此行为封装到仅在必要时执行。 - DragonJawad
2
各位注意:为此添加一个快速界面会让生活变得更加美好,而且幸运的是这很容易实现。 - DragonJawad
如何在MainActivity.java中的onKeyPreIme()方法中调用一个方法? - Abhigyan
在活动中,您不需要调用onKeyPreIme。如果您在活动中使用CustomEditText,则当您按下键盘上的任何按钮时,该方法将被触发。 - Alican Temel
1
我认为这是最好的答案,因为实现起来更加简单。事实上,您只需复制“CustomEditText”类(并在XML中重命名您的edittext),然后在// TODO区域中做你想要的事情。如果您不想关闭活动,请在TODO区域中添加"return true"。谢谢! - Chrysotribax
显示剩余5条评论

78

是的,完全可以显示和隐藏键盘,并拦截对后退按钮的调用。虽然没有直接的API方法来实现此功能,但仍需付出一些额外的努力。关键是在布局中覆盖 boolean dispatchKeyEventPreIme(KeyEvent) 方法。我们要做的是创建我们的布局。我选择了RelativeLayout,因为它是我的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">

在我们的Activity中设置输入字段并调用setActivity(...)函数。

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

显然,initInputField()函数设置了输入框,并启用回车键执行功能(在我的情况下是搜索)。

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

因此,当在我们的布局中调用onBackPressed()时,我们可以执行任何操作,例如隐藏键盘:

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

无论如何,这是我对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);
    }
}

不幸的是我不能完全拥有这个功劳。如果您检查Android的快速搜索对话框的源代码,您将看到这个想法是从哪里来的。


2
这个很好地工作了,并且实现起来并不太难(尽管看起来有点吓人)。 - Cesar
非常感谢这个,我只是希望能够找到一种方法使它能够在旧版的安卓系统上运行。 - zidarsk8
3
只需将 getContext() 强制转换为 Activity,就可以简化代码。当然,前提是布局的上下文确实是所涉及的活动(即当前活动)。但我不知道这是否可能。 - mxcl
3
静态的mSearchActivity可能存在问题,如果布局中有两个该变量怎么办?请改用非静态变量。 - Pointer Null
运行得非常好。但是,我认为最好使用本地静态接口,并从内部活动设置其实现,而不是使用静态活动... - SpiralDev
在我的 Android 6、7 和 8 上运行良好。 - user5698345

13

我发现,覆盖Layout类的dispatchKeyEventPreIme方法也很有效。只需将您的主Activity设置为属性并启动预定义的方法即可。

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

你能给我展示一下该布局的XML吗?我想检查一下这种方法,而我的自定义布局中有SurfaceView,但它无法拦截任何内容。 - ZZZ
基本上,自定义视图是XML中的包围元素。 <?xml version="1.0" encoding="utf-8"?> <view class="package.LinearLayoutGradient"... - Kirill Rakhman
我们也可以通过扩展单个视图并覆盖它的dispatchKeyEventPreIme()方法来实现,而不需要扩展布局本身。 - faizal
这种方法对我很有效。我扩展了我感兴趣的布局,然后传递了一个回调接口而不是活动的引用。 - speedynomads

7

我通过重写 dispatchKeyEvent 方法成功了:

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

它隐藏键盘并完成活动。

这似乎只适用于某些设备,如HTC Desire。 - Abhijit
5
三星Note 2上无法工作。当软键盘弹出时,dispatchKeyEvent()方法不会被调用。 - faizal
好的,我尝试了Nexus 6模拟器(Lollipop 5.0)-> 成功了,但是在Note 4(CM12 Lollipop 5.0.2)上却没有成功,所以这不是最好的解决方案 - 不管怎样.. 谢谢 :) - Martin Pfeffer
当关闭键盘时,按钮点击事件会被消耗。 - slott

4

听起来不错,但是showSoftInput()在我的手机和模拟器上无法显示键盘,所以我使用toggleSoftInput() - Sergey Glotov
1
对我不起作用。onReceiveResult() 被调用时带有 RESULT_SHOWN,但后面从未带有 RESULT_HIDDEN。 - Yulia Rogovaya

2

我曾经遇到过同样的问题,但通过拦截后退键按下来解决了。在我的情况下(HTC Desire, Android 2.2, Application API Level 4),它会关闭键盘并立即完成活动。不知道为什么这对你也不起作用:

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

这在三星Galaxy Note 2上无效。当软键盘可见且按下返回按钮时,onkeydown()或onbackpressed()不会被触发。 - faizal

1
使用 onKeyPreIme(int keyCode, KeyEvent event) 方法并检查 KeyEvent.KEYCODE_BACK 事件。 这很简单,不需要进行任何花式编程。

0

我对@mhradek解决方案的版本:

布局

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文件

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

片段

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        (view as BazingaLayout).activity = activity
        super.onViewCreated(view, savedInstanceState)
}

0

在你的 BackPressed 实现中尝试这段代码(阻止 Android 返回按钮):

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

我建议你看一下关闭/隐藏Android软键盘


1
我已经尝试过这个方法,但仍然需要按两次返回按钮。据我所知,第一次点击被软键盘拦截,因此 onBackPressed() 无法正常工作。只有在第二次按下后,程序才会进入 onBackPressed() 方法。 - Sergey Glotov
@Sergey Glotov,我尝试了很多解决方案,最终得出了一个不太好的解决方案,需要实现自己的软键盘。但我希望安卓社区能很快提供更好的解决方案。 - 100rabh

0

这是我对@kirill-rakhman的解决方案的变体。

当键盘显示时,我需要知道硬件或手势返回按钮被按下的时间,以便我可以做出反应并显示之前隐藏的按钮,当任何编辑文本视图获得焦点时。

  1. 首先声明你所需的回调接口
interface KeyboardEventListener {
   fun onKeyBoardDismissedIme()
}

2. 然后创建带有预输入法键事件监听器的自定义视图。
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. 使用自定义布局视图包装您的布局
<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. 接下来在您的活动或片段中实现回调接口,并设置键盘感知布局
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
    }
}


你有什么想法,为什么在API 33中这个可能不起作用? - Andrey Rankov

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