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

276 lines
7.2 KiB
C#
Raw Normal View History

2025-05-30 10:32:08 +08:00
using System;
2025-03-12 20:59:12 +08:00
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
namespace AlicizaX.UI.RecyclerView
{
2025-05-30 10:32:08 +08:00
[Serializable]
2025-03-12 20:59:12 +08:00
public class Scroller : MonoBehaviour, IScroller, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler
{
protected float position;
2025-05-30 19:40:12 +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;
2025-05-30 19:40:12 +08:00
2025-03-12 20:59:12 +08:00
public Direction Direction
{
get => direction;
set => direction = value;
}
/// <summary>
/// 内容所需要大小
/// </summary>
protected Vector2 contentSize;
2025-05-30 19:40:12 +08:00
2025-03-12 20:59:12 +08:00
public Vector2 ContentSize
{
get => contentSize;
set => contentSize = value;
}
/// <summary>
/// 所在 View 的真实大小
/// </summary>
protected Vector2 viewSize;
2025-05-30 19:40:12 +08:00
2025-03-12 20:59:12 +08:00
public Vector2 ViewSize
{
get => viewSize;
set => viewSize = value;
}
protected float scrollSpeed = 1f;
2025-05-30 19:40:12 +08:00
2025-03-12 20:59:12 +08:00
public float ScrollSpeed
{
get => scrollSpeed;
set => scrollSpeed = value;
}
protected float wheelSpeed = 30f;
2025-05-30 19:40:12 +08:00
2025-03-12 20:59:12 +08:00
public float WheelSpeed
{
get => wheelSpeed;
set => wheelSpeed = value;
}
protected bool snap;
2025-05-30 19:40:12 +08:00
2025-03-12 20:59:12 +08:00
public bool Snap
{
get => snap;
set => snap = value;
}
protected ScrollerEvent scrollerEvent = new();
protected MoveStopEvent moveStopEvent = new();
protected DraggingEvent draggingEvent = new();
2025-05-30 19:40:12 +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-05-30 19:40:12 +08:00
public ScrollerEvent OnValueChanged
{
get => scrollerEvent;
set => scrollerEvent = value;
}
2025-03-12 20:59:12 +08:00
2025-05-30 19:40:12 +08:00
public MoveStopEvent OnMoveStoped
{
get => moveStopEvent;
set => moveStopEvent = value;
}
2025-03-12 20:59:12 +08:00
2025-05-30 19:40:12 +08:00
public DraggingEvent OnDragging
{
get => draggingEvent;
set => draggingEvent = value;
}
2025-03-12 20:59:12 +08:00
// 停止滑动的时间,但此时并未释放鼠标按键
private float dragStopTime = 0f;
public virtual void ScrollTo(float position, bool smooth = false)
{
if (position == this.position) return;
if (!smooth)
{
this.position = position;
OnValueChanged?.Invoke(this.position);
}
else
{
StopAllCoroutines();
StartCoroutine(MoveTo(position));
}
}
public virtual void ScrollToRatio(float ratio)
{
ScrollTo(MaxPosition * ratio, false);
}
public void OnBeginDrag(PointerEventData eventData)
{
OnDragging?.Invoke(true);
StopAllCoroutines();
}
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-05-30 19:40:12 +08:00
public virtual void OnScrolled(PointerEventData eventData)
2025-03-12 20:59:12 +08:00
{
StopAllCoroutines();
float rate = GetScrollRate() * wheelSpeed;
velocity = direction == Direction.Vertical ? -eventData.scrollDelta.y * rate : eventData.scrollDelta.x * rate;
position += velocity;
OnValueChanged?.Invoke(position);
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));
}
2025-05-30 19:40:12 +08:00
2025-03-12 20:59:12 +08:00
return rate;
}
/// <summary>
/// 松手时的惯性滑动
/// </summary>
protected virtual void Inertia()
{
// 松手时的时间 离 停止滑动的时间 超过一定时间,则认为此次惯性滑动无效
if (!snap && (Time.time - dragStopTime) > 0.01f) return;
if (Mathf.Abs(velocity) > 0.1f)
{
StopAllCoroutines();
StartCoroutine(InertiaTo());
}
else
{
OnMoveStoped?.Invoke();
}
}
/// <summary>
/// 滑动到顶部/底部之后,松手时回弹
/// </summary>
protected virtual void Elastic()
{
if (position < 0)
{
StopAllCoroutines();
StartCoroutine(ElasticTo(0));
}
else if (position > MaxPosition)
{
StopAllCoroutines();
StartCoroutine(ElasticTo(MaxPosition));
}
}
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 new WaitForEndOfFrame();
}
OnMoveStoped?.Invoke();
}
IEnumerator ElasticTo(float targetPos)
{
yield return ToPosition(targetPos, 7);
}
IEnumerator MoveTo(float targetPos)
{
yield return ToPosition(targetPos, scrollSpeed);
}
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 new WaitForEndOfFrame();
}
position = targetPos;
OnValueChanged?.Invoke(position);
}
2025-05-30 19:40:12 +08:00
void IScrollHandler.OnScroll(PointerEventData eventData)
{
OnScrolled(eventData);
}
2025-03-12 20:59:12 +08:00
}
}