「Unity3D」使用C#获取Android虚拟键盘的高度

原理是:利用getWindowVisibleDisplayFrame方法,获取Android窗口可见区域的Rect,这个Rect剔除了状态栏与导航栏,并且在有虚拟键盘遮挡的时候,会剔除这个遮挡区域。

接着,UnitysafeArea也剔除了状态栏与导航栏,且不会剔除虚拟键盘遮挡——那么,safeArea.height - getWindowVisibleDisplayFrame.height,就是虚拟键盘的高度。

public static float GetKeyboardHeight()
{
    #if UNITY_ANDROID && !UNITY_EDITOR
    using var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    using var activity         = unityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity");
    using var window           = activity.Call<AndroidJavaObject>("getWindow");
    using var decorView        = window.Call<AndroidJavaObject>("getDecorView");     
    using var rect             = new AndroidJavaObject("android.graphics.Rect");

    decorView.Call("getWindowVisibleDisplayFrame", rect);

    return Screen.safeArea.height - rect.Call<int>("height");
    #else
    return TouchScreenKeyboard.area.height;
    #endif
}

在实际中的问题是,虚拟键盘有动画,getWindowVisibleDisplayFrame获取有延迟,所以需要不断调用GetKeyboardHeight(),大概20帧左右,才能获取虚拟键盘高度的变化——于是AndroidJava对象,就会反复创建与释放。

一个解决方案是,使用协程,即只在高度变化时才返回(利用IEnumeratorCurrent,也可以用回调函数),如下:

/// <summary>
/// Waits until the keyboard height is different from the [oldHeight], and return the new height with [IEnumerator.Current].
/// By using Coroutine to reduce the call and dispose of Java objects. 
/// </summary>
public static IEnumerator GetKeyboardHeight(float oldHeight)
{
    #if UNITY_ANDROID && !UNITY_EDITOR
    using var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    using var activity         = unityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity");
    using var window           = activity.Call<AndroidJavaObject>("getWindow");
    using var decorView        = window.Call<AndroidJavaObject>("getDecorView");     
    using var rect             = new AndroidJavaObject("android.graphics.Rect");

    while (true)
    {
        decorView.Call("getWindowVisibleDisplayFrame", rect);
        var keyboardHeight = Screen.safeArea.height - rect.Call<int>("height");

        if (oldHeight != keyboardHeight)
        {
            yield return keyboardHeight;
            yield break;
        }

        yield return null;
    }
    #else
    yield return TouchScreenKeyboard.area.height;
    #endif
}

再给出一个,可以响应虚拟键盘不同状态的版本, 而safeArea.height也可以放到循环检测外面。

/// <summary>
/// Waits until the keyboard height is different from the [oldHeight], and return the new height with [IEnumerator.Current].
/// If [IEnumerator.Current] is 0.0f, the keyboard needs to be closed.
/// By using Coroutine to reduce the call and dispose of Java objects. 
/// </summary>
public static IEnumerator GetKeyboardHeight(float oldHeight, TouchScreenKeyboard keyboard)
{
    #if UNITY_ANDROID //&& !UNITY_EDITOR
    using var unityPlayerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    using var activity         = unityPlayerClass.GetStatic<AndroidJavaObject>("currentActivity");
    using var window           = activity.Call<AndroidJavaObject>("getWindow");
    using var decorView        = window.Call<AndroidJavaObject>("getDecorView");     
    using var rect             = new AndroidJavaObject("android.graphics.Rect");
          var safeAreaHeight   = Screen.safeArea.height;

    while (true)
    {
        switch (keyboard.status)
        {
            case TouchScreenKeyboard.Status.Visible:
                decorView.Call("getWindowVisibleDisplayFrame", rect);
                var keyboardHeight = safeAreaHeight - rect.Call<int>("height");

                if (oldHeight != keyboardHeight)
                {
                    yield return keyboardHeight;
                    yield break;
                }                    
                break;

            case TouchScreenKeyboard.Status.Done:
            case TouchScreenKeyboard.Status.Canceled:
            case TouchScreenKeyboard.Status.LostFocus:
                yield return 0.0f;
                yield break;
        }

        yield return null;
    }
    #else
    yield return TouchScreenKeyboard.area.height;
    #endif
}