Unity 6でTMP_InputFieldがキーボードに隠れる問題と対処法
はじめに
Unity 6(6000.x)のAndroidビルドにおいて、TMP_InputFieldのHide Mobile Input設定が正しく機能しないケースが報告されています。
本来、Hide Mobile Input = falseに設定するとキーボード上部にネイティブの入力欄が表示され、入力中の文字を確認できるはずです。実際、Unity 6.0(6000.0.x)ではこの設定は正しく機能していました。しかし、Unity 6.3(6000.3.x)にアップデートすると同じ設定が動作しなくなり、キーボードによってInputFieldが隠れてしまいます。
この記事では、キーボードの高さを取得してUIを自動的に上にずらすことで、InputFieldがキーボードに隠れないようにする方法を紹介します。
環境
- Unity 6000.3.x
- TextMeshPro (TMP_InputField)
- Android / iOS
問題の詳細
TMP_InputFieldには以下の2つのプロパティがあります。
| プロパティ | 役割 |
|---|---|
shouldHideMobileInput |
ネイティブ入力欄の表示/非表示 |
shouldHideSoftKeyboard |
ソフトキーボード自体の表示/非表示 |
shouldHideMobileInput = falseにすればキーボード上部にネイティブ入力欄が表示されるはずですが、Unity 6.3のAndroidでは表示されません。これはUnity Issue Trackerにも複数件報告されている既知の問題です。
参考:
- https://issuetracker.unity3d.com/issues/android-tmp-inputfield-selection-highlight-appears-above-keyboard-when-hide-mobile-input-enabled
- https://discussions.unity.com/t/hide-mobile-input-does-not-work/669884
対処法: キーボードの高さに応じてUIをずらす
ネイティブ入力欄に頼らず、キーボードが表示されたときにUI自体を上に移動させることで、InputFieldが隠れないようにします。
実装方針
-
Update()でInputFieldのフォーカス状態を監視する - フォーカス中にキーボードの高さを取得する
- InputFieldの下端がキーボードに隠れているか判定する
- 隠れている場合のみ、必要な分だけUIを上にずらす
- フォーカスが外れたら元の位置に戻す
コード
以下のスクリプトを、InputFieldを含むUI(パネルやスクリーン)にアタッチしてください。
using TMPro;
using UnityEngine;
public class KeyboardAvoidance : MonoBehaviour
{
[SerializeField] private TMP_InputField[] inputFields;
[SerializeField] private float padding = 20f;
private RectTransform rectTransform;
private Vector2 originalAnchoredPosition;
private bool isKeyboardVisible;
private Canvas cachedCanvas;
private readonly Vector3[] cornersBuffer = new Vector3[4];
#if UNITY_ANDROID && !UNITY_EDITOR
private AndroidJavaObject cachedDecorView;
private AndroidJavaObject cachedVisibleRect;
#endif
void OnEnable()
{
rectTransform = GetComponent<RectTransform>();
originalAnchoredPosition = rectTransform.anchoredPosition;
cachedCanvas = GetComponentInParent<Canvas>();
#if UNITY_ANDROID && !UNITY_EDITOR
using var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
using var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
using var window = activity.Call<AndroidJavaObject>("getWindow");
cachedDecorView = window.Call<AndroidJavaObject>("getDecorView");
cachedVisibleRect = new AndroidJavaObject("android.graphics.Rect");
#endif
}
void OnDisable()
{
ResetPosition();
#if UNITY_ANDROID && !UNITY_EDITOR
cachedDecorView?.Dispose();
cachedDecorView = null;
cachedVisibleRect?.Dispose();
cachedVisibleRect = null;
#endif
}
private void ResetPosition()
{
if (rectTransform != null)
rectTransform.anchoredPosition = originalAnchoredPosition;
isKeyboardVisible = false;
}
void Update()
{
// フォーカス中のInputFieldを取得
TMP_InputField focusedField = GetFocusedField();
if (focusedField != null)
{
int keyboardHeight = GetKeyboardHeight();
if (keyboardHeight > 0)
{
float canvasScale = cachedCanvas.scaleFactor;
// InputFieldの下端のスクリーン座標を取得
RectTransform fieldRect = focusedField.GetComponent<RectTransform>();
fieldRect.GetWorldCorners(cornersBuffer);
// cornersBuffer[0]が左下
float fieldBottom = cachedCanvas.worldCamera != null
? cachedCanvas.worldCamera.WorldToScreenPoint(cornersBuffer[0]).y
: cornersBuffer[0].y;
// InputFieldがキーボードに隠れている場合のみ、隠れている分だけ上にずらす
float overlap = keyboardHeight - fieldBottom;
if (overlap > 0)
{
float offset = (overlap + padding) / canvasScale;
rectTransform.anchoredPosition = originalAnchoredPosition + new Vector2(0, offset);
}
isKeyboardVisible = true;
}
}
else if (isKeyboardVisible)
{
ResetPosition();
}
}
private TMP_InputField GetFocusedField()
{
foreach (var field in inputFields)
{
if (field.gameObject.activeInHierarchy && field.isFocused)
return field;
}
return null;
}
private int GetKeyboardHeight()
{
#if UNITY_ANDROID && !UNITY_EDITOR
cachedDecorView.Call("getWindowVisibleDisplayFrame", cachedVisibleRect);
int visibleHeight = cachedVisibleRect.Call<int>("height");
return Screen.height - visibleHeight;
#else
return (int)TouchScreenKeyboard.area.height;
#endif
}
}
使い方
- InputFieldを含むパネル(RectTransform)にこのスクリプトをアタッチする
- InspectorのInput Fieldsに対象の
TMP_InputFieldを登録する -
TMP_InputFieldのInspectorでHide Mobile InputをONにする(ネイティブ入力欄は使わない) -
Hide Soft KeyboardはOFFのままにする
補足
Androidのキーボード高さ取得について
AndroidではTouchScreenKeyboard.areaが正確な値を返さないことがあるため、getWindowVisibleDisplayFrameを使ってネイティブAPIから直接キーボード高さを取得しています。AndroidJavaObjectは毎フレーム生成するとGCアロケーションが発生するため、OnEnableでキャッシュしOnDisableでDisposeしています。
iOSについて
iOSではTouchScreenKeyboard.area.heightが正確な値を返すため、#elseブロックでそのまま使用しています。追加のネイティブプラグインは不要です。
paddingについて
InputFieldがキーボードのギリギリに配置されると見づらいため、paddingで余白を追加しています。Inspectorから調整可能です。
まとめ
- Unity 6.3のAndroidでは
Hide Mobile Input = falseが正しく機能しない - キーボードの高さを取得してUIをずらすことで対処できる
- Androidでは
getWindowVisibleDisplayFrameでキーボード高さを正確に取得する - GC負荷を避けるため
AndroidJavaObjectはキャッシュする