How to scroll to a specific element in ScrollRect with Unity UI?

后端 未结 10 2265
野的像风
野的像风 2021-02-12 04:03

I built a registration form for a mobile game using Unity 5.1. To do that, I use Unity UI components: ScrollRect + Autolayout (Vertical layout) + Text (labels)

10条回答
  •  囚心锁ツ
    2021-02-12 04:23

    A variant of Peter Morris answer for ScrollRects which have movement type "elastic". It bothered me that the scroll rect kept animating for edge cases (first or last few elements). Hope it's useful:

    /// 
    /// Thanks to https://stackoverflow.com/a/50191835
    /// 
    /// 
    /// 
    /// 
    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);
    }
    

    It introduces a one frame delay though.

    UPDATE: Here is a version which does not introduce a frame delay and it also takes scaling of the content into account:

    /// 
    /// Based on https://stackoverflow.com/a/50191835
    /// 
    /// 
    /// 
    /// 
    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();
    }
    
    /// 
    /// Converts a Rect from one RectTransfrom to another RectTransfrom.
    /// Hint: use the root Canvas Transform as "to" to get the reference pixel positions.
    /// 
    /// 
    /// 
    /// 
    public static Rect TransformRectFromTo(Transform from, Transform to)
    {
        RectTransform fromRectTrans = from.GetComponent();
        RectTransform toRectTrans = to.GetComponent();
    
        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);
    }
    

提交回复
热议问题