当Android/iOS中键盘打开时自动调整ScrollView大小

3
我看过很多关于这个问题的主题,但目前为止我找不到任何有效的解决方法。我有一个竖屏应用程序,其中包含大多数输入字段的表单。
我已经创建了一个ScrollView,并在里面添加了所有必要的字段。我添加了垂直布局组和内容大小适配器。现在,当我运行应用程序时,内容可以很好地移动,但是当我打开键盘时,它会覆盖内容,而且我在编辑下面的输入字段时无法看到它们。这看起来不太好。因此,我正在寻找一个脚本/插件,以启用android和iOS的此功能。我只看到了iOS特定的解决方案和通用解决方案,但都没有对我起作用。我可以链接我找到的所有代码,但我认为这只会创造出一个不必要的混乱。这些主题中的大部分都是旧的,可能以前可以工作,但现在不起作用。
理想情况下,我希望有一个全局解决方案,当键盘打开时,整个应用程序都会缩小,当键盘关闭时再扩展回来。
顺便说一句,我正在使用Unity 2019.2.15f1,并在Pixel 3XL上运行Android 10,如果这很重要。
编辑:
我创建了一个小型演示项目,您可以在其中测试键盘大小脚本:

https://drive.google.com/file/d/1vj2WG2JA1OHPc3uI4PNyAeYHtuHTLUXh/view?usp=sharing

它包含3个脚本: ScrollContent.cs - 它以编程方式将InputH.cs脚本附加到每个输入字段。 InputH.cs - 通过调用KeyboardSize脚本中的OpenKeyboard / CloseKeyboard处理单个输入字段的开始(OnPointerClick)和结束(onEndEdit)。 KeyboardSize.cs - 来自@Remy_rm的脚本,稍作修改(添加了一些日志,IsKeyboardOpened方法和我的试图调整键盘滚动位置)。
这个想法看起来不错,但有几个问题:
1)“added”高度似乎有效,但是滚动内容也应该移动(我在GetKeyboard高度方法的最后一行尝试修复它,但它不起作用)。如果您滚动到底部输入字段并点击它,则打开键盘后,此字段应该位于其上方。
2)当我第二次在另一个输入字段上点击时,而第一个输入字段正在编辑时,会导致onEndEdit被调用并关闭键盘。
布局层次结构如下: enter image description here

请查看我的编辑... - Makalele
我查看了您的演示项目。 您的scrollRect没有滚动到所选的inputfield的原因是因为您尝试设置scrollRect.content.anchoredPosition。 为了向下滚动,您需要设置的是scrollRect.verticalNormalizedPosition https://docs.unity3d.com/2017.3/Documentation/ScriptReference/UI.ScrollRect-verticalNormalizedPosition.html 它接受0到1之间的值来设置滚动量。 我已更新我的答案以反映这一点。 - Remy
1个回答

3

假设您正在使用本机 TouchScreenKeyboard ,本答案是基于此做出的。

您可以将一个alpha值设为0(使其不可见)且高度设为0的图像添加为您的垂直布局组的最后一个条目(我们称之为“缓冲区”)。这将使您的布局组看起来没有改变。

然后,当您打开键盘时,您将此“缓冲区”图像的高度设置为键盘的高度。由于内容大小调整器和垂直布局组,您表单的输入字段将被推到键盘上方,“在”您的键盘背后将是空白图像。

在iOS上获取键盘的高度很容易TouchScreenKeyboard.area.height应该能解决问题。但是,在Android上,这会返回一个空矩形。这个回答解释了如何获取Android键盘的高度。

完全实现后,它看起来应该像这样(只在Android上测试过,但也应该适用于iOS)。请注意,我正在使用来自前面链接答案的方法来获取Android键盘高度。

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class KeyboardSize : MonoBehaviour
{
    [SerializeField] private RectTransform bufferImage;

    private float height = -1;

    /// <summary>
    /// Open the keyboard and start a coroutine that gets the height of the keyboard
    /// </summary>
    public void OpenKeyboard()
    {
        TouchScreenKeyboard.Open("");
        StartCoroutine(GetKeyboardHeight());
    }

    /// <summary>
    /// Set the height of the "buffer" image back to zero when the keyboard closes so that the content size fitter shrinks to its original size
    /// </summary>
    public void CloseKeyboard()
    {
        bufferImage.GetComponent<RectTransform>().sizeDelta = Vector2.zero;
    }

    /// <summary>
    /// Get the height of the keyboarding depending on the platform
    /// </summary>
    public IEnumerator GetKeyboardHeight()
    {
        //Wait half a second to ensure the keyboard is fully opened
        yield return new WaitForSeconds(0.5f);

#if UNITY_IOS
        //On iOS we can use the native TouchScreenKeyboard.area.height
        height = TouchScreenKeyboard.area.height;

#elif UNITY_ANDROID
        //On Android TouchScreenKeyboard.area.height returns 0, so we get it from an AndroidJavaObject instead.
        using (AndroidJavaClass UnityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
        {
            AndroidJavaObject View = UnityClass.GetStatic<AndroidJavaObject>("currentActivity").Get<AndroidJavaObject>("mUnityPlayer").Call<AndroidJavaObject>("getView");

            using (AndroidJavaObject Rct = new AndroidJavaObject("android.graphics.Rect"))
            {
                View.Call("getWindowVisibleDisplayFrame", Rct);

                height = Screen.height - Rct.Call<int>("height");
            }
        }
#endif
        //Set the height of our "buffer" image to the height of the keyboard, pushing it up.
        bufferImage.sizeDelta = new Vector2(1, height);
    }

    StartCoroutine(CalculateNormalizedPosition());

}

private IEnumerator CalculateNormalizedPosition()
{
    yield return new WaitForEndOfFrame();
    //Get the new total height of the content gameobject
    var newContentHeight = contentParent.sizeDelta.y;
    //Get the local y position of the selected input
    var selectedInputHeight = InputH.lastSelectedInput.transform.localPosition.y;
    //Get the normalized position of the selected input
    var selectedInputfieldHeightNormalized = 1 - selectedInputHeight / -newContentHeight;
    //Assign the button's normalized position to the scroll rect's normalized position
    scrollRect.verticalNormalizedPosition = selectedInputfieldHeightNormalized;
}

我已经编辑了你的InputH,通过添加一个静态游戏对象来跟踪最后选择的输入,当点击输入字段时,将其分配给该对象,如下所示:

public class InputH : MonoBehaviour, IPointerClickHandler
{
    private GameObject canvas;

    //We can use a static GameObject to track the last selected input
    public static GameObject lastSelectedInput;

    // Start is called before the first frame update
    void Start()
    {
        // Your start is unaltered
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        KeyboardSize ks = canvas.GetComponent<KeyboardSize>();
        if (!ks.IsKeyboardOpened())
        {
            ks.OpenKeyboard();

            //Assign the clicked button to the lastSelectedInput to be used inside KeyboardSize.cs
            lastSelectedInput = gameObject;
        }
        else
        {
            print("Keyboard is already opened");
        }
    }

还有一件事情是必须要做的(至少对于我的解决方案来说),就是我必须将您“Content”游戏对象上的锚点更改为顶部中心,而不是您之前设定的拉伸enter image description here


1
@Makalele 我已经编辑了我的答案,包括一个计算规范化位置的方法,当选择输入字段时,它将滚动到键盘上方。它可能需要一些偏移调整,但应该可以工作。请注意,我还在您的inputH脚本中进行了小修改,并更改了Content GameObject上的锚点。如果需要,我可以上传您演示项目的版本。由于您的输入字段位于负Y位置,因此某些值(例如newContentHeight)需要被反转。 - Remy
它差不多可以工作了。我假设contentParent是scrollRect.content。当我点击最后一个输入字段时,它会滚动到其中心,因此底部一半被截断。你能上传你的演示项目版本吗? - Makalele
以下是关于编程的相关内容的翻译,仅返回翻译后的文本:https://drive.google.com/open?id=1eiNEPTOTHN0ceewfKcOevC1hOvVZqWvA 这里是它的下载链接@Makalele - Remy
@Makalele 是的,正如他们在文档中所述,这取决于用户...“因为键盘的外观可能会遮挡您的用户界面的某些部分,所以您需要确保在显示键盘时不会遮挡您用户界面的任何部分。” 可能是因为让它正常工作非常麻烦...要将其靠近键盘,您可以添加一个高度偏移量。像 height -= 50 这样的东西可能会起到作用?我想知道分辨率差异如何影响定位... - Remy
也许是凹口的问题,很难说。目前对我来说已经足够了,但我肯定会回到这个话题并加以完善。 - Makalele
显示剩余2条评论

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