0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity 6.3】HideMobileInput が動作しない場合の回避策

0
Posted at

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にも複数件報告されている既知の問題です。

参考:

対処法: キーボードの高さに応じてUIをずらす

ネイティブ入力欄に頼らず、キーボードが表示されたときにUI自体を上に移動させることで、InputFieldが隠れないようにします。

実装方針

  1. Update()でInputFieldのフォーカス状態を監視する
  2. フォーカス中にキーボードの高さを取得する
  3. InputFieldの下端がキーボードに隠れているか判定する
  4. 隠れている場合のみ、必要な分だけUIを上にずらす
  5. フォーカスが外れたら元の位置に戻す

コード

以下のスクリプトを、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
    }
}

使い方

  1. InputFieldを含むパネル(RectTransform)にこのスクリプトをアタッチする
  2. InspectorのInput Fieldsに対象のTMP_InputFieldを登録する
  3. TMP_InputFieldのInspectorでHide Mobile InputをONにする(ネイティブ入力欄は使わない)
  4. 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はキャッシュする
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?