如何使用Unity UI滚动到ScrollRect中的特定元素?

时间:2015-06-10 19:53:17

标签: user-interface unity3d unity3d-gui

我使用Unity 5.1为移动游戏构建了一个注册表单。 为此,我使用 Unity UI 组件:ScrollRect + Autolayout(垂直布局)+文本(标签)+输入字段。 这部分工作正常。

但是,当键盘打开时,所选字段位于键盘下方。有没有办法以编程方式滚动表单以使选定的字段进入视图?

我已尝试使用ScrollRect.verticalNormalizedPosition并且可以正常滚动一些,但是我无法将所选字段显示在我想要的位置。

感谢您的帮助!

10 个答案:

答案 0 :(得分:26)

我打算给你一个我的代码片段,因为我觉得自己很有帮助。希望这有帮助!

protected ScrollRect scrollRect;
protected RectTransform contentPanel;

public void SnapTo(RectTransform target)
    {
        Canvas.ForceUpdateCanvases();

        contentPanel.anchoredPosition =
            (Vector2)scrollRect.transform.InverseTransformPoint(contentPanel.position)
            - (Vector2)scrollRect.transform.InverseTransformPoint(target.position);
    }

答案 1 :(得分:5)

尽管@maksymiuk的答案是最正确的,但由于InverseTransformPoint()函数,它正确地考虑了锚点,枢轴和所有其他因素,但对于我来说,它仍然不能立即使用-对于垂直滚动条,它也在更改其X位置。因此,我只是进行更改以检查是否启用了垂直滚动或水平滚动,如果没有启用,则不更改其轴。

public static void SnapTo( this ScrollRect scroller, RectTransform child )
{
    Canvas.ForceUpdateCanvases();

    var contentPos = (Vector2)scroller.transform.InverseTransformPoint( scroller.content.position );
    var childPos = (Vector2)scroller.transform.InverseTransformPoint( child.position );
    var endPos = contentPos - childPos;
    // If no horizontal scroll, then don't change contentPos.x
    if( !scroller.horizontal ) endPos.x = contentPos.x;
    // If no vertical scroll, then don't change contentPos.y
    if( !scroller.vertical ) endPos.y = contentPos.y;
    scroller.content.anchoredPosition = endPos;
}

答案 2 :(得分:4)

这些建议都不适用于我,以下代码

这是扩展名

using UnityEngine;
using UnityEngine.UI;

namespace BlinkTalk
{
    public static class ScrollRectExtensions
    {
        public static Vector2 GetSnapToPositionToBringChildIntoView(this ScrollRect instance, RectTransform child)
        {
            Canvas.ForceUpdateCanvases();
            Vector2 viewportLocalPosition = instance.viewport.localPosition;
            Vector2 childLocalPosition   = child.localPosition;
            Vector2 result = new Vector2(
                0 - (viewportLocalPosition.x + childLocalPosition.x),
                0 - (viewportLocalPosition.y + childLocalPosition.y)
            );
            return result;
        }
    }
}

以下是我用它将内容的直接子项滚动到视图中的方式

    private void Update()
    {
        MyScrollRect.content.localPosition = MyScrollRect.GetSnapToPositionToBringChildIntoView(someChild);
    }

答案 3 :(得分:2)

如果有人想要平滑滚动(使用lerp)。

[SerializeField]
private ScrollRect _scrollRectComponent; //your scroll rect component
[SerializeField]
RectTransform _container; //content transform of the scrollrect
private IEnumerator LerpToChild(RectTransform target)
{
    Vector2 _lerpTo = (Vector2)_scrollRectComponent.transform.InverseTransformPoint(_container.position) - (Vector2)_scrollRectComponent.transform.InverseTransformPoint(target.position);
    bool _lerp = true;
    Canvas.ForceUpdateCanvases();

    while(_lerp)
    {
        float decelerate = Mathf.Min(10f * Time.deltaTime, 1f);
        _container.anchoredPosition = Vector2.Lerp(_scrollRectComponent.transform.InverseTransformPoint(_container.position), _lerpTo, decelerate);
        if (Vector2.SqrMagnitude((Vector2)_scrollRectComponent.transform.InverseTransformPoint(_container.position) - _lerpTo) < 0.25f)
        {
            _container.anchoredPosition = _lerpTo;
            _lerp = false;
        }
        yield return null;
    }
}

答案 4 :(得分:1)

这就是我将所选对象固定到ScrollRect

的方式
private ScrollRect scrollRect;
private RectTransform contentPanel;

public void ScrollReposition(RectTransform obj)
{
    var objPosition = (Vector2)scrollRect.transform.InverseTransformPoint(obj.position);
    var scrollHeight = scrollRect.GetComponent<RectTransform>().rect.height;
    var objHeight = obj.rect.height;

    if (objPosition.y > scrollHeight / 2)
    {
        contentPanel.localPosition = new Vector2(contentPanel.localPosition.x,
            contentPanel.localPosition.y - objHeight - Padding.top);
    }

    if (objPosition.y < -scrollHeight / 2)
    {
        contentPanel.localPosition = new Vector2(contentPanel.localPosition.x,
contentPanel.localPosition.y + objHeight + Padding.bottom);
    }
}

答案 5 :(得分:1)

我这个问题的版本的前提:

  • 我要滚动到的element应该完全可见(最小间隙)
  • elementscrollRect内容的直接子代
  • 如果element已经完全可见,请保持scoll位置
  • 我只关心垂直尺寸

这是最适合我的方法(感谢其他灵感):

// ScrollRect scrollRect;
// RectTransform element;
// Fully show `element` inside `scrollRect` with at least 25px clearance
scrollArea.EnsureVisibility(element, 25);

使用此扩展方法:

public static void EnsureVisibility(this ScrollRect scrollRect, RectTransform child, float padding=0)
{
    Debug.Assert(child.parent == scrollRect.content,
        "EnsureVisibility assumes that 'child' is directly nested in the content of 'scrollRect'");

    float viewportHeight = scrollRect.viewport.rect.height;
    Vector2 scrollPosition = scrollRect.content.anchoredPosition;

    float elementTop = child.anchoredPosition.y;
    float elementBottom = elementTop - child.rect.height;

    float visibleContentTop = -scrollPosition.y - padding;
    float visibleContentBottom = -scrollPosition.y - viewportHeight + padding;

    float scrollDelta =
        elementTop > visibleContentTop ? visibleContentTop - elementTop :
        elementBottom < visibleContentBottom ? visibleContentBottom - elementBottom :
        0f;

    scrollPosition.y += scrollDelta;
    scrollRect.content.anchoredPosition = scrollPosition;
}

答案 6 :(得分:1)

彼得·莫里斯(Peter Morris)对ScrollRect的回答的变体,其运动类型为“弹性”。令我烦恼的是,滚动矩形一直为边缘情况(前几个元素或后几个元素)设置动画。希望它有用:

/// <summary>
/// Thanks to https://stackoverflow.com/a/50191835
/// </summary>
/// <param name="instance"></param>
/// <param name="child"></param>
/// <returns></returns>
public static IEnumerator BringChildIntoView(this UnityEngine.UI.ScrollRect instance, RectTransform child)
{
    Canvas.ForceUpdateCanvases();
    Vector2 viewportLocalPosition = instance.viewport.localPosition;
    Vector2 childLocalPosition = child.localPosition;
    Vector2 result = new Vector2(
        0 - (viewportLocalPosition.x + childLocalPosition.x),
        0 - (viewportLocalPosition.y + childLocalPosition.y)
    );
    instance.content.localPosition = result;

    yield return new WaitForUpdate();

    instance.horizontalNormalizedPosition = Mathf.Clamp(instance.horizontalNormalizedPosition, 0f, 1f);
    instance.verticalNormalizedPosition = Mathf.Clamp(instance.verticalNormalizedPosition, 0f, 1f);
}

虽然它引入了一帧延迟。

更新:这是一个不引入帧延迟并且还考虑内容缩放的版本:

/// <summary>
/// Based on https://stackoverflow.com/a/50191835
/// </summary>
/// <param name="instance"></param>
/// <param name="child"></param>
/// <returns></returns>
public static void BringChildIntoView(this UnityEngine.UI.ScrollRect instance, RectTransform child)
{
    instance.content.ForceUpdateRectTransforms();
    instance.viewport.ForceUpdateRectTransforms();

    // now takes scaling into account
    Vector2 viewportLocalPosition = instance.viewport.localPosition;
    Vector2 childLocalPosition = child.localPosition;
    Vector2 newContentPosition = new Vector2(
        0 - ((viewportLocalPosition.x * instance.viewport.localScale.x) + (childLocalPosition.x * instance.content.localScale.x)),
        0 - ((viewportLocalPosition.y * instance.viewport.localScale.y) + (childLocalPosition.y * instance.content.localScale.y))
    );

    // clamp positions
    instance.content.localPosition = newContentPosition;
    Rect contentRectInViewport = TransformRectFromTo(instance.content.transform, instance.viewport);
    float deltaXMin = contentRectInViewport.xMin - instance.viewport.rect.xMin;
    if(deltaXMin > 0) // clamp to <= 0
    {
        newContentPosition.x -= deltaXMin;
    }
    float deltaXMax = contentRectInViewport.xMax - instance.viewport.rect.xMax;
    if (deltaXMax < 0) // clamp to >= 0
    {
        newContentPosition.x -= deltaXMax;
    }
    float deltaYMin = contentRectInViewport.yMin - instance.viewport.rect.yMin;
    if (deltaYMin > 0) // clamp to <= 0
    {
        newContentPosition.y -= deltaYMin;
    }
    float deltaYMax = contentRectInViewport.yMax - instance.viewport.rect.yMax;
    if (deltaYMax < 0) // clamp to >= 0
    {
        newContentPosition.y -= deltaYMax;
    }

    // apply final position
    instance.content.localPosition = newContentPosition;
    instance.content.ForceUpdateRectTransforms();
}

/// <summary>
/// Converts a Rect from one RectTransfrom to another RectTransfrom.
/// Hint: use the root Canvas Transform as "to" to get the reference pixel positions.
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <returns></returns>
public static Rect TransformRectFromTo(Transform from, Transform to)
{
    RectTransform fromRectTrans = from.GetComponent<RectTransform>();
    RectTransform toRectTrans = to.GetComponent<RectTransform>();

    if (fromRectTrans != null && toRectTrans != null)
    {
        Vector3[] fromWorldCorners = new Vector3[4];
        Vector3[] toLocalCorners = new Vector3[4];
        Matrix4x4 toLocal = to.worldToLocalMatrix;
        fromRectTrans.GetWorldCorners(fromWorldCorners);
        for (int i = 0; i < 4; i++)
        {
            toLocalCorners[i] = toLocal.MultiplyPoint3x4(fromWorldCorners[i]);
        }

        return new Rect(toLocalCorners[0].x, toLocalCorners[0].y, toLocalCorners[2].x - toLocalCorners[1].x, toLocalCorners[1].y - toLocalCorners[0].y);
    }

    return default(Rect);
}

答案 7 :(得分:0)

是的,这可以使用编码垂直滚动,请尝试以下代码:

//Set Scrollbar Value - For Displaying last message of content
Canvas.ForceUpdateCanvases ();
verticleScrollbar.value = 0f;
Canvas.ForceUpdateCanvases ();

当我开发聊天功能时,此代码对我来说很好。

答案 8 :(得分:0)

width表示滚动矩形中子级的宽度(假设所有子级的宽度都相同),间距表示子级之间的间距,索引表示您要达到的目标元素

public float getSpecificItem (float pWidth, float pSpacing,int pIndex) {
    return (pIndex * pWidth) - pWidth + ((pIndex - 1) * pSpacing);
}

答案 9 :(得分:0)

垂直调整:

[SerializeField]
private ScrollRect _scrollRect;

private void ScrollToCurrentElement()
{
    var siblingIndex = _currentListItem.transform.GetSiblingIndex();

    float pos = 1f - (float)siblingIndex / _scrollRect.content.transform.childCount;

    if (pos < 0.4)
    {
        float correction = 1f / _scrollRect.content.transform.childCount;
        pos -= correction;
    }

    _scrollRect.verticalNormalizedPosition = pos;
}