如何在安卓系统中检查软键盘的可见性?

559

我需要做一件非常简单的事情——找出软键盘是否显示。这在安卓系统中可行吗?


9
尽管Reuben Scratton的回答很好,但在平板电脑上似乎有问题。我将检查diff> 128替换为diff> screenHeight/3。 - kingston
2
Reuben Scratton的回答很好,但我需要KaChi的调整才能真正使用它。 - Cullan
2
为什么谷歌不为所有的键盘应用程序制作一个通用的内置方法? - 林果皞
5
我很在意这不是一个系统功能的事实。 - longi
1
这个API仍然缺失了10年,真是太疯狂了。非常高兴我已经远离Android了。 - Reuben Scratton
显示剩余7条评论
45个回答

4

有一个隐藏的方法可以帮助解决这个问题,InputMethodManager.getInputMethodWindowVisibleHeight。但我不知道为什么它被隐藏了。

import android.content.Context
import android.os.Handler
import android.view.inputmethod.InputMethodManager

class SoftKeyboardStateWatcher(private val ctx: Context) {
  companion object {
    private const val DELAY = 10L
  }

  private val handler = Handler()
  private var isSoftKeyboardOpened: Boolean = false

  private val height: Int
    get() {
      val imm = ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
      val method = imm.javaClass.getMethod("getInputMethodWindowVisibleHeight")
      method.isAccessible = true
      return method.invoke(imm) as Int
    }

  private val task: Runnable by lazy {
    Runnable {
      start()
      if (!isSoftKeyboardOpened && height > 0) {
        isSoftKeyboardOpened = true
        notifyOnSoftKeyboardOpened(height)
      } else if (isSoftKeyboardOpened && height == 0) {
        isSoftKeyboardOpened = false
        notifyOnSoftKeyboardClosed()
      }
    }
  }

  var listener: SoftKeyboardStateListener? = null

  interface SoftKeyboardStateListener {
    fun onSoftKeyboardOpened(keyboardHeightInPx: Int)
    fun onSoftKeyboardClosed()
  }

  fun start() {
    handler.postDelayed(task, DELAY)
  }

  fun stop() {
    handler.postDelayed({
      if (!isSoftKeyboardOpened) handler.removeCallbacks(task)
    }, DELAY * 10)
  }

  private fun notifyOnSoftKeyboardOpened(keyboardHeightInPx: Int) {
    listener?.onSoftKeyboardOpened(keyboardHeightInPx)
  }

  private fun notifyOnSoftKeyboardClosed() {
    listener?.onSoftKeyboardClosed()
  }
}

如果有人需要的话 - 它在 Xamarin 中也可以工作,方法的名称完全相同,并且需要通过 InputMethodManager 上的 Class 属性以相同的方式访问。 - Konstantin Severy
使用时要小心,这是不受支持的API(它被隐藏有其原因),而且对于初学者来说,在KitKat上无法正常工作。 - Daniele Ricci

4

我没有在我的应用程序中使用菜单选项,而是像这样做,不假设编码差异。

final View root= findViewById(R.id.myrootview); 
root.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
    public void onGlobalLayout() {
        int heightDiff = root.getRootView().getHeight() - root.getHeight();

        Rect rectgle= new Rect();
        Window window= getWindow();
        window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
        int contentViewTop=                     
          window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
        if(heightDiff <= contentViewTop){
            //Soft KeyBoard Hidden
        }else{
            //Soft KeyBoard Shown
        }
     }
});

它在android:windowSoftInputMode="adjustPan"上不起作用。我希望软键盘出现后,我的屏幕不会缩小。您能否告诉我任何修复方法,以便即使进行adjustPan也可以正常工作? - Shirish Herwade

4
您可以使用activity的decorView观察软键盘的隐藏。
public final class SoftKeyboardUtil {
    public static final String TAG = "SoftKeyboardUtil";
    public static void observeSoftKeyBoard(Activity activity , final OnSoftKeyBoardHideListener listener){
        final View decorView = activity.getWindow().getDecorView();
        decorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Rect rect = new Rect();
                decorView.getWindowVisibleDisplayFrame(rect);
                int displayHight = rect.bottom - rect.top;
                int hight = decorView.getHeight();
                boolean hide = (double)displayHight / hight > 0.8 ;
                if(Log.isLoggable(TAG, Log.DEBUG)){
                    Log.d(TAG ,"DecorView display hight = "+displayHight);
                    Log.d(TAG ,"DecorView hight = "+ hight);
                    Log.d(TAG, "softkeyboard visible = " + !hide);
                }

                listener.onSoftKeyBoardVisible(!hide);

            }
        });
    }



    public interface OnSoftKeyBoardHideListener{
        void onSoftKeyBoardVisible(boolean visible);
    }
}

3

我发现将@Reuben_Scratton的方法和@Yogesh的方法结合起来似乎效果最好。将它们两个的方法结合起来,可以得到如下结果:

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
  @Override
  public void onGlobalLayout() {
    if (getResources().getConfiguration().keyboardHidden == Configuration.KEYBOARDHIDDEN_NO) { // Check if keyboard is not hidden
       // ... do something here
    }
  }
});

始终为 Configuration.KEYBOARDHIDDEN_NO。 - fantouch

3

尝试这个:

final View activityRootView = getWindow().getDecorView().getRootView();
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        Rect r = new Rect();
        //r will be populated with the coordinates of your view that area still visible.
        activityRootView.getWindowVisibleDisplayFrame(r);

        int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
        if (heightDiff < activityRootView.getRootView().getHeight() / 4 ) { // if more than 100 pixels, its probably a keyboard...
             // ... do something here ... \\
        }
    }
});

3

这些解决方案对于Lollipop来说都行不通。在Lollipop中,activityRootView.getRootView().getHeight()包括按钮栏的高度,而测量视图则不包括它。我已经将上面最好/最简单的解决方案调整为适用于Lollipop。

    final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
  @Override
  public void onGlobalLayout() {
    Rect r = new Rect();
    //r will be populated with the coordinates of your view that area still visible.
    activityRootView.getWindowVisibleDisplayFrame(r);

    int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
    Resources res = getResources();
    // The status bar is 25dp, use 50dp for assurance
    float maxDiff =
        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, res.getDisplayMetrics());

    //Lollipop includes button bar in the root. Add height of button bar (48dp) to maxDiff
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      float buttonBarHeight =
          TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, res.getDisplayMetrics());
      maxDiff += buttonBarHeight;
    }
    if (heightDiff > maxDiff) { // if more than 100 pixels, its probably a keyboard...
      ...do something here
    }
  }
});

为什么 https://dev59.com/WHI95IYBdhLWcg3wyRM0#18992807 上的类似解决方案不适用于您,并且您的解决方案有何区别? - CoolMind

3

我使用了Reuban答案的变种,在某些情况下,特别是在高分辨率设备上,这种方法更加有效。

final View activityRootView = findViewById(android.R.id.content);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(
        new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                int heightView = activityRootView.getHeight();
                int widthView = activityRootView.getWidth();
                if (1.0 * widthView / heightView > 3) {
                    //Make changes for Keyboard not visible
                } else {
                    //Make changes for keyboard visible
                }
            }
        });

这个 R.id.activityRoot 是什么? - ranjith
2
不需要创建和使用 R.id.activityRoot,你可以直接使用 android.R.id.content,它正是你所需要的。 - Marcin Orlowski

3

在使用上述大部分建议添加固定数字的解决方案时,我刚刚遇到了一个错误。

S4具有较高dpi,导致导航栏高度为100px,因此我的应用程序认为键盘一直处于打开状态。

因此,随着发布越来越多的新高分辨率手机,我认为长期使用硬编码值不是一个好主意。

在多个屏幕和设备上进行了一些测试后,我发现更好的方法是使用百分比。获取decorView和应用程序内容之间的差异,然后检查该差异的百分比。从我得到的统计数据来看,大多数导航栏(无论大小、分辨率等)将占据屏幕的3%到5%。而如果键盘打开,则会占用47%到55%的屏幕空间。

作为结论,我的解决方案是检查差异是否超过10%,然后我假设它是一个打开的键盘。


3

在计算机领域,这个问题已经存在了很久,但它仍然是不可思议的相关!因此,我已经综合并完善了以上答案...

public interface OnKeyboardVisibilityListener {
    void onVisibilityChanged(boolean visible);
}

public final void setKeyboardListener(final OnKeyboardVisibilityListener listener) {
    final View activityRootView = ((ViewGroup) getActivity().findViewById(android.R.id.content)).getChildAt(0);
    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        private boolean wasOpened;

    private final Rect r = new Rect();

        @Override
        public void onGlobalLayout() {
            activityRootView.getWindowVisibleDisplayFrame(r);

            int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
            boolean isOpen = heightDiff > 100;
            if (isOpen == wasOpened) {
                logDebug("Ignoring global layout change...");
                return;
            }

            wasOpened = isOpen;
            listener.onVisibilityChanged(isOpen);
        }
    });
}

这对我有效。


3

根据文档.. https://developer.android.com/reference/androidx/core/view/WindowInsetsCompat

查看发布说明.. https://developer.android.com/jetpack/androidx/releases/core#1.5.0-alpha02

要获取当前键盘的可见性,可以使用getRootWindowInsets,然后调用 isVisible() 函数,并传入IME类型。

val windowinsetscompat = ViewCompat.getRootWindowInsets(view)
val imeVisible = windowinsetscompat.isVisible(Type.ime())

还有一种用于监听变化的方法是 OnApplyWindowInsetsListener

ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
    val imeVisible = insets.isVisible(Type.ime())
}

“isVisible”方法来自哪里?我遇到了“未解决的引用”错误。我的天啊,我讨厌安卓和谷歌。 - Andrew
@Andrew 尝试在 build.gradle 中添加 implementation 'androidx.core:core-ktx:1.5.0-alpha02' - Jaydeep chatrola
我已经找到了,谢谢。但是这该死的函数不起作用... "OnApplyWindowInsets"从未被调用... API 21 - Andrew
好吧,如果那是情况的话,我将永远退出Android开发。 - Andrew

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