首页 > 解决方案 > 在 Unity 中使用捏合手势缩放滚动视图内容的正确方法是什么?

问题描述

目标:制作一个通用组件,支持捏合放大设备和滚动鼠标放大编辑器。捏合时,将枢轴设置为两根手指的中点,使其围绕您捏合的位置缩放。

我在以下内容中提供了我的脚本。但是,当它“跳跃”或抖动很多时,它在两指捏合的设备上的滚动视图中效果不佳。不过,它适用于编辑器中的鼠标滚动。我认为原因可能与滚动视图如何在内部更新布局有关。在单个框架中,我的脚本更改了内容的轴心和位置,但是滚动视图无法正确处理这些更改。这就是为什么我还使用 1 LateUpdate、2 产生 WaitForEndOfFrame、3 在滚动视图的 OnValueChanged 事件中注册回调来测试我的脚本,但都失败了。

有谁知道如何解决我的脚本中的问题或任何其他新解决方案如何制作滚动视图以支持捏缩放?谢谢!

我的代码是这样的:

using System.Collections; 
using System.Collections.Generic; 
using UnityEngine; 
using UnityEngine.EventSystems;

public class PinchZoom : MonoBehaviour {
    public float zoomSpeedPinch = 0.001f;
    public float zoomSpeedMouseScrollWheel = 0.05f;
    public float zoomMin = 0.1f;
    public float zoomMax = 1f;
    RectTransform rectTransform;
    public int type = 1; // for device testing type 1 use LateUpdate; type 2 use Update

    private void Awake()
    {
        rectTransform = GetComponent<RectTransform>();
    }

    //public void OnValueChanged(Vector2 v) // test failed: called by scroll view event
    //{
    //    //Zoom();
    //}

    void Update()
    {
        //Zoom();
        if (type == 2)
        {
            if (Input.touchCount == 2)
                StartCoroutine(ZoomInTheEndOfFrame(Input.mouseScrollDelta.y, Input.touchCount, Input.GetTouch(0), Input.GetTouch(1), Input.mousePosition));
            else
                StartCoroutine(ZoomInTheEndOfFrame(Input.mouseScrollDelta.y, Input.touchCount, default(Touch), default(Touch), Input.mousePosition));
        }
    }

    private void LateUpdate()
    {
        if (type == 1) Zoom();
    }

    void Zoom()
    {
        var mouseScrollWheel = Input.mouseScrollDelta.y;
        float scaleChange = 0f;
        Vector2 midPoint = Vector2.zero;
        if (Input.touchCount == 2)
        {
            Touch touchZero = Input.GetTouch(0);
            Touch touchOne = Input.GetTouch(1);

            Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
            Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;

            float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
            float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;

            float deltaMagnitudeDiff = touchDeltaMag - prevTouchDeltaMag;

            scaleChange = deltaMagnitudeDiff * zoomSpeedPinch;

            midPoint = (touchOne.position + touchZero.position) / 2;
        }

        if (mouseScrollWheel != 0)
        {
            scaleChange = mouseScrollWheel * zoomSpeedMouseScrollWheel;
            midPoint = Input.mousePosition;
        }

        if (scaleChange != 0)
        {
            var scaleX = transform.localScale.x;
            scaleX += scaleChange;
            scaleX = Mathf.Clamp(scaleX, zoomMin, zoomMax);
            var size = rectTransform.rect.size;
            size.Scale(rectTransform.localScale);
            var parentRect = ((RectTransform)rectTransform.parent);
            var parentSize = parentRect.rect.size;
            parentSize.Scale(parentRect.localScale);
            if (size.x > parentSize.x && size.y > parentSize.y)
            {
                var p1 = Camera.main.ScreenToWorldPoint(midPoint); 
                var p2 = transform.InverseTransformPoint(p1); 
                var pivotP = rectTransform.pivot * rectTransform.rect.size; 
                var p3 = (Vector2)p2 + pivotP; 
                var newPivot = p3 / rectTransform.rect.size;
                newPivot = new Vector2(Mathf.Clamp01(newPivot.x), Mathf.Clamp01(newPivot.y));
                rectTransform.SetPivot(newPivot);
            }
            else
            {
                rectTransform.SetPivot(new Vector2(0.5f, 0.5f));
            }

            transform.localScale = new Vector3(scaleX, scaleX, transform.localScale.z);
        }
    }

    //private IEnumerator ZoomInTheEndOfFrame(float mouseScrollWheel, int touchCount, Touch touchZero, Touch touchOne, Vector3 mousePosition) // testing failed
    //{
    //    yield return new WaitForEndOfFrame();
    //    ZoomWithData(mouseScrollWheel, touchCount, touchZero, touchOne, mousePosition);
    //}

}

为了在不让图像“跳跃”的情况下更改枢轴,我使用了扩展脚本:

using UnityEngine;

public static class RectTranformExtension
{
    /// <summary>
    /// Set pivot without changing the position of the element
    /// </summary>
    public static void SetPivot(this RectTransform rectTransform, Vector2 pivot)
    {
        Vector3 deltaPosition = rectTransform.pivot - pivot;    // get change in pivot
        deltaPosition.Scale(rectTransform.rect.size);           // apply sizing
        deltaPosition.Scale(rectTransform.localScale);          // apply scaling
        deltaPosition = rectTransform.rotation * deltaPosition; // apply rotation

        rectTransform.pivot = pivot;                            // change the pivot
        rectTransform.localPosition -= deltaPosition;           // reverse the position change
    }
}

标签: c#unity3d

解决方案


经过几天的努力,我终于解决了。Zoom()解决方法是在缩放时调用OnDrag(),并阻止scollView组件接收拖拽相关事件。如果你想使用它,只需复制我的代码,不要做任何更改。这是我在设备上进行大量测试后得到的。有一些潜在的小问题,我不想解释太多细节,复制并使用它。而且我还建议使用当前 content.scale.x * 0.001 的速度

代码:

// please note that scrollRect is the component on the scroll view game object, not where this script is

public void OnDrag(PointerEventData eventData)
{
    Zoom();
    if (Input.touchCount <= 1) scrollRect.OnDrag(eventData);
}

public void OnEndDrag(PointerEventData eventData)
{
    scrollRect.OnEndDrag(eventData);
}

public void OnBeginDrag(PointerEventData eventData)
{
    if (Input.touchCount <= 1) scrollRect.OnBeginDrag(eventData);
}

PS 在原始脚本中,有一部分如果未通过边界检查,则将枢轴设置为(0.5,0.5)。这部分应该被注释掉(如果检查不通过则不需要更改枢轴)。

还有一件事,你可以使用任何你想要的父子关系,但是你必须确保接收 onDrag 事件的对象有一个完整的图像来捕捉事件。


推荐阅读