c# - 在 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
}
}
解决方案
经过几天的努力,我终于解决了。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 事件的对象有一个完整的图像来捕捉事件。
推荐阅读
- traefik - 我可以扩展 Traefik 以打开请求标头检查用户身份并更新请求 url 吗?
- javascript - 如何在angularjs语法es6中的两个组件之间传递数据?
- rest - 如何从外部 Web 表单调用 Salesforce REST API
- excel - 每次单击 excel 上的按钮时,让我的输入移动的最简单方法是什么?
- azure-functions - 使用 Webhook 响应作为助手响应的一部分
- azure - Azure Function App - 尝试从 v2 工具发布到 v1 函数应用
- ruby-on-rails - 使用分块请求时如何强制关闭 excon 连接
- python - Firestore python函数没有被触发
- sql-server - 将数据库更改移植到 SQL Server Reporting Services 以避免破坏报告
- visual-c++ - 我在使用嵌套的 for 循环格式化打印语句时遇到问题