Goal: Make a general component that supports pinching to zoom on device and scroll mouse to zoom in editor. While pinching, set pivot to the middle point of two fingers, so it scales around where you pinch.
I provided my scripts in the followings. However it does not work well in scroll view on devices with two fingers pinching, when it "jumps" or jitters a lot. It works with mouse scroll in the Editor though. I think the reason is perhaps related with how the scroll view updates the layout internally. In a single frame my scripts changed the pivot and position of the content however the scroll view can not handle these changes properly. That's why I also tested my scripts using 1 LateUpdate, 2 yield WaitForEndOfFrame, 3 register call back in scroll view's OnValueChanged event, but all failed.
Does anyone know how to fix the problem in my script or any other new solutions how to make a scroll view to support pinch to zoom? Thanks!
My codes are something like this:
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);
//}
}
For changing pivot without get the image "jumped" I used an extension script:
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
}
}