2026-03-24 11:39:01 +08:00

98 lines
4.6 KiB
C#

using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/* Snap a scroll rect to its children items. All self contained.
* Note: Supports 1 direction only.
*/
namespace eToile_example
{
[RequireComponent(typeof(ScrollRect))]
public class DragSnapper : UIBehaviour, IEndDragHandler, IBeginDragHandler
{
public SnapDirection direction; // The scroll direction.
public AnimationCurve decelerationCurve = AnimationCurve.Linear(0f, 0f, 1f, 1f); // Transition curve (easing).
public float speed = 0.5f; // The speed in which we snap.
public float snapPercentage = 0.2f; // Percent to snal to next streen.
public int startItem = 0; // First item to show.
ScrollRect scrollRect;
int itemCount; // Item count in our scroll rect (2 minimum)
float value; // Last valid snapped position.
int target; // Last target snap position.
new void Start()
{
itemCount = transform.Find("Container").childCount;
// Set first item view:
scrollRect = gameObject.GetComponent<ScrollRect>();
target = startItem;
scrollRect.normalizedPosition = new Vector2(startItem / (float)itemCount, 0f);
value = scrollRect.normalizedPosition.x;
// Set ScrollRect scroll direction:
if (direction == SnapDirection.Horizontal)
{
scrollRect.horizontal = true;
scrollRect.vertical = false;
}
else if (direction == SnapDirection.Vertical)
{
scrollRect.horizontal = false;
scrollRect.vertical = true;
}
}
// The direction we are snapping in:
public enum SnapDirection
{
Horizontal,
Vertical,
}
public void OnBeginDrag(PointerEventData eventData)
{
StopCoroutine(SnapRect()); // If we are snapping, stop for the next input.
}
public void OnEndDrag(PointerEventData eventData)
{
StartCoroutine(SnapRect());
}
private IEnumerator SnapRect()
{
if (itemCount < 2) print("Item count must be 2 or more");
float delta = 1f / (itemCount - 1); // Item percentage (just in case they change dynamically).
float startNormal = direction == SnapDirection.Horizontal ? scrollRect.horizontalNormalizedPosition : scrollRect.verticalNormalizedPosition;
float tempDelta = (startNormal - value) / delta; // Displacement percentage.
// Closest item depending on the current position:
target = Mathf.RoundToInt(value / delta);
if (tempDelta > snapPercentage) target++;
else if (tempDelta < -snapPercentage) target--;
target = Mathf.Clamp(target, 0, itemCount - 1);
float endNormal = delta * target; // Normalized value for the target item.
float duration = Mathf.Abs((endNormal - startNormal) / speed); // Time needed to get the target depending on speed.
float timer = 0f;
while (timer < 1f) // Animate deceleration.
{
timer = Mathf.Min(1f, timer + Time.deltaTime / duration); // Calculate our timer based on our speed.
value = Mathf.Lerp(startNormal, endNormal, decelerationCurve.Evaluate(timer)); // Our value based on the animation curve.
if (direction == SnapDirection.Horizontal) // Depending on direction we set our horizontal or vertical position.
scrollRect.horizontalNormalizedPosition = value;
else
scrollRect.verticalNormalizedPosition = value;
yield return new WaitForEndOfFrame(); // Wait until next frame.
}
}
}
}