如何检测Android设备上的软键盘是否可见?

331

在Android中,有没有一种方法可以检测软键盘是否显示在屏幕上?


2
可能是重复的问题:Android:如何判断软键盘是否显示? - Heath Hunnicutt
1
在某些情况下(如果安装了第三方键盘),解决方案可以是检查全局通知,因为当键盘打开时,会有一个系统通知显示“更改键盘”。这可以通过NotificationListenerService来完成。 - Prof
3
将近8年了,仍然没有可靠的解决方案。哦,如果他们介绍一个解决方案,它也只能适用于API > 30,所以没关系。 - M.kazem Akhgary
可能是Android:检测软键盘打开的重复问题。 - AbdelHady
如果你正在使用Espresso或Jetpack Compose进行UI测试,你可以使用executeShellCommand()来检测键盘是否显示在屏幕上:https://stackoverflow.com/questions/33970956/test-if-soft-keyboard-is-visible-using-espresso#53118977 - undefined
39个回答

327
这对我来说很有效。也许这总是所有版本的最佳方式。
将键盘可见性作为一个属性,并延迟观察其变化会非常有效,因为onGlobalLayout方法会被多次调用。此外,检查设备旋转和windowSoftInputMode不是adjustNothing也是很好的做法。
boolean isKeyboardShowing = false;
void onKeyboardVisibilityChanged(boolean opened) {
    print("keyboard " + opened);
}

// ContentView is the root view of the layout of this activity/fragment    
contentView.getViewTreeObserver().addOnGlobalLayoutListener(
    new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {

        Rect r = new Rect();
        contentView.getWindowVisibleDisplayFrame(r);
        int screenHeight = contentView.getRootView().getHeight();

        // r.bottom is the position above soft keypad or device button.
        // if keypad is shown, the r.bottom is smaller than that before.
        int keypadHeight = screenHeight - r.bottom;

        Log.d(TAG, "keypadHeight = " + keypadHeight);

        if (keypadHeight > screenHeight * 0.15) { // 0.15 ratio is perhaps enough to determine keypad height.
            // keyboard is opened
            if (!isKeyboardShowing) {
                isKeyboardShowing = true;
                onKeyboardVisibilityChanged(true);
            }
        }
        else {
            // keyboard is closed
            if (isKeyboardShowing) {
                isKeyboardShowing = false;
                onKeyboardVisibilityChanged(false);
            }
        }
    }
});

3
这是一个可用的要点:https://gist.github.com/faruktoptas/e9778e1f718214938b00c2dcd2bed109 - Faruk Toptas
1
将此代码放入一个工具类中,并传入活动 - 现在可在整个应用程序中使用。 - Justin
2
contentView 声明在哪里? - Code-Apprentice
1
@Code-Apprentice 在你想要响应软键盘变化的活动/片段中,ContentView 是该活动/片段布局的根视图。 - airowe
1
onKeyboardVisibilityChanged()函数实际上可以被删除, 最好直接将代码放到onGlobalLayoutListener中。 其余的工作就交给我了。+1 谢谢。 - Edhar Khimich
显示剩余9条评论

85

试试这个:

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

    if (imm.isAcceptingText()) {
        writeToLog("Software Keyboard was shown");
    } else {
        writeToLog("Software Keyboard was not shown");
    }

12
对我来说这行不通。即使键盘从未显示过或者已经显示并关闭,键盘显示分支也会触发。 - Peter Ajtai
37
它总是返回true。 - Shivang Trivedi
244
安卓框架在这方面的缺失和不一致是令人遗憾的。这本应该是非常简单的事情。 - Vicky Chijwani
有些情况下无法正常工作!需要其他东西。 - iscariot
3
这显然行不通,因为它不是监听软键盘行为的监听器。 - 6rchid
显示剩余6条评论

82

我创建了一个简单的类,可用于此目的:https://github.com/ravindu1024/android-keyboardlistener。只需将其复制到您的项目中,并按以下方式使用:

KeyboardUtils.addKeyboardToggleListener(this, new KeyboardUtils.SoftKeyboardToggleListener()
{
    @Override
    public void onToggleSoftKeyboard(boolean isVisible)
    {
        Log.d("keyboard", "keyboard visible: "+isVisible);
    }
});

在代码中我应该把它放在哪里?我把它放在一个活动中,但是它没有检测到任何键盘的出现或消失。 - toom
好的,你可以将它放在活动中的任何位置。只需将其放置在setContentView()调用之后的onCreate()方法中,你应该会得到回调。顺便问一下,你正在尝试在哪个设备上运行它? - ravindu1024
@MaulikDodia 我已经检查过了,它在片段中运行良好。像这样设置:KeyboardUtils.addKeyboardToggleListener(getActivity(), this); 它应该可以工作。你在尝试哪个设备? - ravindu1024
它不是必需的才能工作。但是,由于键盘 util 类持有对您的活动的静态引用以提供该回调,因此您应该取消注册以避免内存泄漏。 - ravindu1024
如果您使用findViewById,则可以完美地工作,但如果使用View Binding,则会出现错误(act.findViewById<View>(… ViewGroup).getChildAt(0) must not be null。有什么建议吗? - Aldan
显示剩余7条评论

79

8
当用户使用分屏应用时,这实际上会导致程序崩溃。 - Nishant Pardamwar
你可以为API 21及以上编写如下的扩展函数。 fun View.isKeyboardVisible(): Boolean { val insets = ViewCompat.getRootWindowInsets(this) return insets?.isVisible(WindowInsetsCompat.Type.ime()) ?: false } - Hitesh Bisht

42

通过androidx core release 1.5.0-alpha02中的新功能WindowInsetsCompat,您可以轻松地检查软键盘的可见性,如下所示。

引用自reddit comment

val View.keyboardIsVisible: Boolean
    get() = WindowInsetsCompat
        .toWindowInsetsCompat(rootWindowInsets)
        .isVisible(WindowInsetsCompat.Type.ime())

关于向后兼容性的一些注释,引用自发布说明

新功能

WindowInsetsCompat API已经更新为Android 11平台中的API。这包括新的ime()插入类型,它允许检查屏幕键盘的可见性和大小。

关于ime()类型的一些注意事项,它在API 23+上非常可靠,当您的Activity使用adjustResize窗口软输入模式时。如果您改用adjustPan模式,则应该能够向后兼容到API 14。

参考资料


1
请查看此视频,该视频还引用了这个问题!https://www.youtube.com/watch?v=acC7SR1EXsI - Tom Gilder
@TomGilder,直接进行编辑并添加视频到参考文献中即可。 - user158
需要至少API 20。 - Saman Sattari
6
为了使它工作,我不得不将 WindowInsetsCompat.toWindowInsetsCompat(rootWindowInsets) 替换为 ViewCompat.getRootWindowInsets(this)。在Android视频中找到了这个方法:https://youtu.be/acC7SR1EXsI?t=319 - AndrazP
1
这对于 Android 23-29 返回 true,即使键盘没有打开,只有在 Android 30 上才能正常工作。 - user924
很遗憾这个答案被埋在底部。所有旧的答案都使用了不再支持或不稳定的API。让我们把它置顶吧! - undefined

32

非常简单

1. 在根视图上放置id

rootView只是一个指向我的根视图的视图,在这种情况下是一个相对布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/addresses_confirm_root_view"
                android:background="@color/WHITE_CLR">

2. 在您的Activity中初始化根视图:

RelativeLayout rootView = (RelativeLayout) findViewById(R.id.addresses_confirm_root_view);

3. 通过使用 getViewTreeObserver() 来检测键盘是否打开或关闭:

    rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                int heightDiff = rootView.getRootView().getHeight() - rootView.getHeight();
                
                if (heightDiff > 100) { // Value should be less than keyboard's height 
                    Log.e("MyActivity", "keyboard opened");
                } else { 
                    Log.e("MyActivity", "keyboard closed");
                }
            }
        });

30
嘿,伙计,你能告诉我这个神奇的100是从哪里来的吗?为什么不是101或99?谢谢。 - narancs
@Karoly,我认为这可能是一个“1”。不要紧。只要这个长度比键盘的实际长度小就可以了。 - Vlad
@Karoly 基本上,他正在比较窗口大小和您的活动根视图大小。软键盘的外观不会影响主窗口的大小。因此,您仍然可以将值降低到100。 - mr5
2
魔数取决于您的顶栏布局等因素。因此,它是相对于您的应用程序而言的。我在我的一个应用程序中使用了400。 - Morten Holmgaard
5
如果你的布局涉及到底部菜单,这将不会有帮助。无法区分键盘和底部菜单。 - Syam Sundar K
显示剩余3条评论

14

经过长时间尝试AccessibilityServices、窗口插图、屏幕高度检测等方法,我想我找到了一种方法来实现这个。

声明:它使用了Android中的一个隐藏方法,意味着可能不稳定。然而,在我的测试中,它似乎有效。

这个方法是InputMethodManager#getInputMethodWindowVisibleHeight(),自Lollipop(5.0)以来就存在。

调用它可以返回当前键盘的高度(以像素为单位)。理论上,键盘不应该为0个像素高,因此我进行了一个简单的高度检查(使用Kotlin):

val imm by lazy { context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager }
if (imm.inputMethodWindowVisibleHeight > 0) {
    //keyboard is shown
else {
    //keyboard is hidden
}

我使用Android Hidden API来调用隐藏方法时避免使用反射(我在开发的应用程序中经常这样做,这些应用程序大多是hacky/tuner应用程序),但这也可以使用反射实现:

val imm by lazy { context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager }
val windowHeightMethod = InputMethodManager::class.java.getMethod("getInputMethodWindowVisibleHeight")
val height = windowHeightMethod.invoke(imm) as Int
//use the height val in your logic

2
反射的使用非常出色。 - kaustubhpatange
他们确实隐藏了这个方法。 - Rafael
1
是的,这个可以工作。可惜我们必须轮询它,而且没有回调告诉我们何时发生了变化。 - Pointer Null

11

您可以使用androidx.core(版本1.5.0-rc01)中的WindowInsetsCompat

此代码将在API 21及以上版本上运行。Kotlin代码示例:

ViewCompat.setOnApplyWindowInsetsListener(root) { v, insets ->
    val isKeyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
    if (isKeyboardVisible) {
    }
}

root 是你的 Activity 的根视图。

更新

今天我在寻找如何检测键盘可见性。起初,这段代码并没有起作用。所以我不得不:

  1. android:windowSoftInputMode="adjustResize" 添加到我的 AndroidManifest.xml 文件中:
xml
        <activity android:name="com.soumicslabs.activitykt.StartActivity"
            android:theme="@style/AccountKitTheme.Default"
          android:configChanges="orientation|screenSize"
          android:screenOrientation="portrait"
          android:windowSoftInputMode="adjustResize"
          />
  1. 在您的活动中,设置WindowCompat.setDecorFitsSystemWindows(window, false),这会告诉安卓我们想要手动处理事物/不想使用系统默认值:
        val window = this.window
        WindowCompat.setDecorFitsSystemWindows(window, false)  // <-- this tells android not to use system defaults, so we have to setup quite a lot of behaviors manually
  1. 最后,设置onApplyWindowInsetsListener
val callBack = OnApplyWindowInsetsListener { view, insets ->
            val imeHeight = insets?.getInsets(WindowInsetsCompat.Type.ime())?.bottom?:0
            Log.e("tag", "onKeyboardOpenOrClose imeHeight = $imeHeight")
// todo: logic
val isKeyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
    if (isKeyboardVisible) {
       // do something
    }else{
        // do something else
    }
    insets?: WindowInsetsCompat(null)
  }

ViewCompat.setOnApplyWindowInsetsListener(mainContainer, callBack)

这对我起作用了。


这个可以工作,但会破坏所有依赖于setDecorFitsSystemWindows的其他布局。 - MeLean

10

我以此为基础:https://rogerkeays.com/how-to-check-if-the-software-keyboard-is-shown-in-android

(意思为,该链接是作者参考的基础资料,未进行具体翻译)
/**
* To capture the result of IMM hide/show soft keyboard
*/
public class IMMResult extends ResultReceiver {
     public int result = -1;
     public IMMResult() {
         super(null);
}

@Override 
public void onReceiveResult(int r, Bundle data) {
    result = r;
}

// poll result value for up to 500 milliseconds
public int getResult() {
    try {
        int sleep = 0;
        while (result == -1 && sleep < 500) {
            Thread.sleep(100);
            sleep += 100;
        }
    } catch (InterruptedException e) {
        Log.e("IMMResult", e.getMessage());
    }
    return result;
}
}

然后编写了这个方法:
public boolean isSoftKeyboardShown(InputMethodManager imm, View v) {
    
    IMMResult result = new IMMResult();
    int res;
    
    imm.showSoftInput(v, 0, result);

    // if keyboard doesn't change, handle the keypress
    res = result.getResult();
    if (res == InputMethodManager.RESULT_UNCHANGED_SHOWN ||
            res == InputMethodManager.RESULT_UNCHANGED_HIDDEN) {

        return true;
    }
    else
        return false;

}

你可以使用这个来测试所有可能打开软键盘的字段(EditText、AutoCompleteTextView等):

    InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
    if(isSoftKeyboardShown(imm, editText1) | isSoftKeyboardShown(imm, autocompletetextview1))
        //close the softkeyboard
        imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);

诚然,这不是一个理想的解决方案,但它可以完成工作。


2
这个可以用。如果你将其实现为单例,就可以应用于所有的EditText焦点变化,并拥有一个全局键盘监听器。 - Rarw
1
@depperm getActivity() 是特定于片段的,请尝试使用 YourActivityName.this。另请参阅:https://dev59.com/NWYq5IYBdhLWcg3wcgPq - Christopher Hackl
Thead.sleep - 这是什么?让主线程休眠? - user924

7

Thread.sleep - 是什么?让主线程休眠? - user924

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