原理是:利用getWindowVisibleDisplayFrame方法,獲取Android窗口可見區域的Rect,這個Rect剔除了狀態欄與導航欄,并且在有虛擬鍵盤遮擋的時候,會剔除這個遮擋區域。
接著,Unity的safeArea也剔除了狀態欄與導航欄,且不會剔除虛擬鍵盤遮擋——那么,safeArea.height -?getWindowVisibleDisplayFrame.height,就是虛擬鍵盤的高度。
public static float GetKeyboardHeight()
{#if UNITY_ANDROID && !UNITY_EDITORusing 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");#elsereturn TouchScreenKeyboard.area.height;#endif
}
在實際中的問題是,虛擬鍵盤有動畫,getWindowVisibleDisplayFrame獲取有延遲,所以需要不斷調用GetKeyboardHeight(),大概20幀左右,才能獲取虛擬鍵盤高度的變化——于是Android的Java對象,就會反復創建與釋放。
一個解決方案是,使用協程,即只在高度變化時才返回(利用IEnumerator的Current,也可以用回調函數),如下:
/// <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_EDITORusing 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;}#elseyield 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_EDITORusing 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;}#elseyield return TouchScreenKeyboard.area.height;#endif
}