com.alicizax.unity.ui.exten.../Runtime/RecyclerView/Scroller/Scroller.cs

289 lines
8.0 KiB
C#
Raw Normal View History

using System.Collections;
2025-03-12 20:59:12 +08:00
using UnityEngine;
using UnityEngine.EventSystems;
namespace AlicizaX.UI
2025-03-12 20:59:12 +08:00
{
public class Scroller : MonoBehaviour, IScroller, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler
{
private static readonly WaitForEndOfFrame EndOfFrameYield = new();
private Coroutine movementCoroutine;
2025-03-12 20:59:12 +08:00
protected float position;
2025-12-26 14:22:46 +08:00
public float Position { get => position; set => position = value; }
2025-03-12 20:59:12 +08:00
protected float velocity;
public float Velocity => velocity;
protected Direction direction;
public Direction Direction
{
get => direction;
set => direction = value;
}
protected Vector2 contentSize;
public Vector2 ContentSize
{
get => contentSize;
set => contentSize = value;
}
protected Vector2 viewSize;
public Vector2 ViewSize
{
get => viewSize;
set => viewSize = value;
}
protected float scrollSpeed = 1f;
public float ScrollSpeed
{
get => scrollSpeed;
set => scrollSpeed = value;
}
protected float wheelSpeed = 30f;
public float WheelSpeed
{
get => wheelSpeed;
set => wheelSpeed = value;
}
protected bool snap;
public bool Snap
{
get => snap;
set => snap = value;
}
protected ScrollerEvent scrollerEvent = new();
protected MoveStopEvent moveStopEvent = new();
protected DraggingEvent draggingEvent = new();
2025-12-26 14:22:46 +08:00
public float MaxPosition => direction == Direction.Vertical ?
Mathf.Max(contentSize.y - viewSize.y, 0) :
Mathf.Max(contentSize.x - viewSize.x, 0);
2025-03-12 20:59:12 +08:00
public float ViewLength => direction == Direction.Vertical ? viewSize.y : viewSize.x;
2025-12-26 14:22:46 +08:00
public ScrollerEvent OnValueChanged { get => scrollerEvent; set => scrollerEvent = value; }
2025-03-12 20:59:12 +08:00
2025-12-26 14:22:46 +08:00
public MoveStopEvent OnMoveStoped { get => moveStopEvent; set => moveStopEvent = value; }
2025-03-12 20:59:12 +08:00
2025-12-26 14:22:46 +08:00
public DraggingEvent OnDragging { get => draggingEvent; set => draggingEvent = value; }
2025-03-12 20:59:12 +08:00
2025-12-26 14:22:46 +08:00
public float dragStopTime = 0f;
2025-03-12 20:59:12 +08:00
2025-03-12 20:59:12 +08:00
public virtual void ScrollTo(float position, bool smooth = false)
{
if (Mathf.Approximately(position, this.position)) return;
StopMovement();
2025-03-12 20:59:12 +08:00
if (!smooth)
{
this.position = position;
OnValueChanged?.Invoke(this.position);
}
else
{
movementCoroutine = StartCoroutine(RunMotion(MoveTo(position)));
2025-03-12 20:59:12 +08:00
}
}
public virtual void ScrollToDuration(float position, float duration)
{
if (Mathf.Approximately(position, this.position))
{
return;
}
StopMovement();
if (duration <= 0f)
{
this.position = position;
OnValueChanged?.Invoke(this.position);
return;
}
movementCoroutine = StartCoroutine(RunMotion(ToPositionByDuration(position, duration)));
}
2025-03-12 20:59:12 +08:00
public virtual void ScrollToRatio(float ratio)
{
ScrollTo(MaxPosition * ratio, false);
}
public void OnBeginDrag(PointerEventData eventData)
{
OnDragging?.Invoke(true);
StopMovement();
2025-03-12 20:59:12 +08:00
}
public void OnEndDrag(PointerEventData eventData)
{
Inertia();
Elastic();
OnDragging?.Invoke(false);
}
public void OnDrag(PointerEventData eventData)
{
dragStopTime = Time.time;
velocity = GetDelta(eventData);
position += velocity;
OnValueChanged?.Invoke(position);
}
2025-12-26 14:22:46 +08:00
public void OnScroll(PointerEventData eventData)
2025-03-12 20:59:12 +08:00
{
StopMovement();
2025-03-12 20:59:12 +08:00
float rate = GetScrollRate() * wheelSpeed;
velocity = direction == Direction.Vertical ? -eventData.scrollDelta.y * rate : eventData.scrollDelta.x * rate;
position += velocity;
OnValueChanged?.Invoke(position);
2025-12-26 14:22:46 +08:00
Inertia();
2025-03-12 20:59:12 +08:00
Elastic();
}
internal virtual float GetDelta(PointerEventData eventData)
{
float rate = GetScrollRate();
return direction == Direction.Vertical ? eventData.delta.y * rate : -eventData.delta.x * rate;
}
private float GetScrollRate()
{
float rate = 1f;
if (position < 0)
{
rate = Mathf.Max(0, 1 - (Mathf.Abs(position) / ViewLength));
}
else if (position > MaxPosition)
{
rate = Mathf.Max(0, 1 - (Mathf.Abs(position - MaxPosition) / ViewLength));
}
return rate;
}
protected virtual void Inertia()
{
if (Mathf.Abs(velocity) > 0.1f)
{
StopMovement();
movementCoroutine = StartCoroutine(RunMotion(InertiaTo()));
2025-03-12 20:59:12 +08:00
}
else
{
OnMoveStoped?.Invoke();
}
}
protected virtual void Elastic()
{
if (position < 0)
{
StopMovement();
movementCoroutine = StartCoroutine(RunMotion(ElasticTo(0)));
2025-03-12 20:59:12 +08:00
}
else if (position > MaxPosition)
{
StopMovement();
movementCoroutine = StartCoroutine(RunMotion(ElasticTo(MaxPosition)));
2025-03-12 20:59:12 +08:00
}
}
IEnumerator InertiaTo()
{
float timer = 0f;
float p = position;
float v = velocity > 0 ? Mathf.Min(velocity, 100) : Mathf.Max(velocity, -100);
float duration = snap ? 0.1f : 1f;
while (timer < duration)
{
float y = (float)EaseUtil.EaseOutCirc(timer) * 40;
timer += Time.deltaTime;
position = p + y * v;
Elastic();
OnValueChanged?.Invoke(position);
yield return EndOfFrameYield;
2025-03-12 20:59:12 +08:00
}
OnMoveStoped?.Invoke();
}
IEnumerator ElasticTo(float targetPos)
{
yield return ToPosition(targetPos, 7);
}
IEnumerator MoveTo(float targetPos)
{
yield return ToPosition(targetPos, scrollSpeed);
}
IEnumerator ToPositionByDuration(float targetPos, float duration)
{
duration = Mathf.Max(duration, 0.0001f);
float startPos = position;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
position = Mathf.Lerp(startPos, targetPos, t);
OnValueChanged?.Invoke(position);
yield return EndOfFrameYield;
}
position = targetPos;
OnValueChanged?.Invoke(position);
}
2025-03-12 20:59:12 +08:00
IEnumerator ToPosition(float targetPos, float speed)
{
float startPos = position;
float time = Time.deltaTime;
while (Mathf.Abs(targetPos - position) > 0.1f)
{
position = Mathf.Lerp(startPos, targetPos, time * speed);
OnValueChanged?.Invoke(position);
time += Time.deltaTime;
yield return EndOfFrameYield;
2025-03-12 20:59:12 +08:00
}
position = targetPos;
OnValueChanged?.Invoke(position);
}
private IEnumerator RunMotion(IEnumerator motion)
{
yield return motion;
movementCoroutine = null;
}
private void StopMovement()
{
if (movementCoroutine == null)
{
return;
}
StopCoroutine(movementCoroutine);
movementCoroutine = null;
}
2025-03-12 20:59:12 +08:00
}
}