临时提交

This commit is contained in:
陈思海 2026-04-29 20:25:58 +08:00
parent 255892dd5f
commit db11a4aa1a
9 changed files with 261 additions and 36 deletions

View File

@ -105,6 +105,26 @@ namespace AlicizaX.UI
return -index; return -index;
} }
public override float GetItemStartPosition(int index)
{
return IndexToPosition(index);
}
public override float GetItemLength(int index)
{
return Mathf.Abs(intervalAngle);
}
public override int GetSnapIndex(float position)
{
if (adapter == null || adapter.GetItemCount() <= 0)
{
return 0;
}
return Mathf.Clamp(PositionToIndex(position), 0, adapter.GetItemCount() - 1);
}
public override void DoItemAnimation() public override void DoItemAnimation()
{ {
int visibleCount = viewProvider.VisibleCount; int visibleCount = viewProvider.VisibleCount;

View File

@ -174,5 +174,50 @@ namespace AlicizaX.UI
return index * unit; return index * unit;
} }
public override float GetItemStartPosition(int index)
{
if (adapter == null || adapter.GetItemCount() <= 0)
{
return 0f;
}
index = Mathf.Clamp(index, 0, adapter.GetItemCount() - 1);
int row = index / unit;
float len = direction == Direction.Vertical ? cellSize.y + spacing.y : cellSize.x + spacing.x;
return row * len;
}
public override float GetItemLength(int index)
{
return direction == Direction.Vertical ? Mathf.Max(cellSize.y, 0f) : Mathf.Max(cellSize.x, 0f);
}
public override int GetSnapIndex(float position)
{
if (adapter == null || adapter.GetItemCount() <= 0)
{
return 0;
}
float len = direction == Direction.Vertical ? cellSize.y + spacing.y : cellSize.x + spacing.x;
float itemLength = GetItemLength(0);
if (len <= 0f || itemLength <= 0f)
{
return 0;
}
int itemCount = adapter.GetItemCount();
int row = Mathf.FloorToInt(Mathf.Max(position, 0f) / len);
int index = Mathf.Clamp(row * unit, 0, itemCount - 1);
float visibleLength = itemLength - (position - GetItemStartPosition(index));
if (visibleLength < itemLength * 0.5f && index + unit < itemCount)
{
index += unit;
}
return index;
}
} }
} }

View File

@ -26,6 +26,12 @@ namespace AlicizaX.UI
int PositionToIndex(float position); int PositionToIndex(float position);
float GetItemStartPosition(int index);
float GetItemLength(int index);
int GetSnapIndex(float position);
void DoItemAnimation(); void DoItemAnimation();
bool IsFullVisibleStart(int index); bool IsFullVisibleStart(int index);

View File

@ -142,6 +142,20 @@ namespace AlicizaX.UI
public abstract int PositionToIndex(float position); public abstract int PositionToIndex(float position);
public abstract float GetItemStartPosition(int index);
public abstract float GetItemLength(int index);
public virtual int GetSnapIndex(float position)
{
if (adapter == null || adapter.GetItemCount() <= 0)
{
return 0;
}
return Mathf.Clamp(PositionToIndex(position), 0, adapter.GetItemCount() - 1);
}
public virtual void DoItemAnimation() { } public virtual void DoItemAnimation() { }
public virtual bool IsFullVisibleStart(int index) public virtual bool IsFullVisibleStart(int index)

View File

@ -143,5 +143,49 @@ namespace AlicizaX.UI
return index; return index;
} }
public override float GetItemStartPosition(int index)
{
if (adapter == null || adapter.GetItemCount() <= 0)
{
return 0f;
}
index = Mathf.Clamp(index, 0, adapter.GetItemCount() - 1);
float len = direction == Direction.Vertical ? lineHeight + spacing.y : lineHeight + spacing.x;
return index * len;
}
public override float GetItemLength(int index)
{
return Mathf.Max(lineHeight, 0f);
}
public override int GetSnapIndex(float position)
{
if (adapter == null || adapter.GetItemCount() <= 0)
{
return 0;
}
float len = direction == Direction.Vertical ? lineHeight + spacing.y : lineHeight + spacing.x;
float itemLength = GetItemLength(0);
if (len <= 0f || itemLength <= 0f)
{
return 0;
}
int itemCount = adapter.GetItemCount();
int index = Mathf.FloorToInt(Mathf.Max(position, 0f) / len);
index = Mathf.Clamp(index, 0, itemCount - 1);
float visibleLength = itemLength - (position - GetItemStartPosition(index));
if (visibleLength < itemLength * 0.5f && index < itemCount - 1)
{
index++;
}
return index;
}
} }
} }

View File

@ -145,6 +145,62 @@ namespace AlicizaX.UI
return index >= 0 ? index : cachedItemCount - 1; return index >= 0 ? index : cachedItemCount - 1;
} }
public override float GetItemStartPosition(int index)
{
EnsurePositionCache();
if (cachedItemCount <= 0)
{
return 0f;
}
index = Mathf.Clamp(index, 0, cachedItemCount - 1);
return itemPositions[index];
}
public override float GetItemLength(int index)
{
EnsurePositionCache();
if (cachedItemCount <= 0)
{
return 0f;
}
index = Mathf.Clamp(index, 0, cachedItemCount - 1);
float spacingLength = index < cachedItemCount - 1
? (direction == Direction.Vertical ? spacing.y : spacing.x)
: 0f;
return Mathf.Max(itemLengths[index] - spacingLength, 0f);
}
public override int GetSnapIndex(float position)
{
EnsurePositionCache();
if (cachedItemCount <= 0)
{
return 0;
}
int index = FindFirstItemEndingAfter(Mathf.Max(position, 0f));
if (index < 0)
{
return cachedItemCount - 1;
}
float itemLength = GetItemLength(index);
if (itemLength <= 0f)
{
return index;
}
float visibleLength = itemLength - (position - GetItemStartPosition(index));
if (visibleLength < itemLength * 0.5f && index < cachedItemCount - 1)
{
index++;
}
return index;
}
private void EnsurePositionCache() private void EnsurePositionCache()
{ {
int itemCount = adapter != null ? adapter.GetItemCount() : 0; int itemCount = adapter != null ? adapter.GetItemCount() : 0;

View File

@ -1203,12 +1203,11 @@ namespace AlicizaX.UI
return scroller.Position; return scroller.Position;
} }
float itemSize = GetItemSize(index); float itemSize = layoutManager.GetItemLength(index);
float viewportLength = direction == Direction.Vertical ? layoutManager.ViewportSize.y : layoutManager.ViewportSize.x; float viewportLength = direction == Direction.Vertical ? layoutManager.ViewportSize.y : layoutManager.ViewportSize.x;
float contentLength = direction == Direction.Vertical ? layoutManager.ContentSize.y : layoutManager.ContentSize.x;
// 计算目标项的原始位置不在此阶段做范围限制<E99990>? // 计算目标项的原始位置不在此阶段做范围限制<E99990>?
float itemPosition = CalculateRawItemPosition(index); float itemPosition = layoutManager.GetItemStartPosition(index);
float targetPosition = alignment switch float targetPosition = alignment switch
{ {
@ -1276,13 +1275,7 @@ namespace AlicizaX.UI
/// </summary> /// </summary>
private void OnMoveStoped() private void OnMoveStoped()
{ {
if (snap && SnapToNearestItem()) HandleScrollSettled();
{
return;
}
TryProcessPendingFocusRequest();
OnScrollStopped?.Invoke();
} }
/// <summary> /// <summary>
@ -1313,10 +1306,7 @@ namespace AlicizaX.UI
{ {
if (scroller == null) return; if (scroller == null) return;
if (scroller.Position < scroller.MaxPosition && snap) HandleScrollSettled();
{
SnapToNearestItem();
}
} }
#endregion #endregion
@ -1603,17 +1593,36 @@ namespace AlicizaX.UI
/// <returns>触发了新的吸附滚动时返回 <see langword="true"/>否则返<E58899>?<see langword="false"/><3E>?/returns> /// <returns>触发了新的吸附滚动时返回 <see langword="true"/>否则返<E58899>?<see langword="false"/><3E>?/returns>
private bool SnapToNearestItem() private bool SnapToNearestItem()
{ {
int index = layoutManager.PositionToIndex(GetScrollPosition()); if (layoutManager == null || RecyclerViewAdapter == null || RecyclerViewAdapter.GetItemCount() <= 0)
float targetPosition = layoutManager.IndexToPosition(index);
if (Mathf.Abs(targetPosition - GetScrollPosition()) <= 0.1f)
{ {
return false; return false;
} }
ScrollTo(index, true); float position = GetScrollPosition();
int index = layoutManager.GetSnapIndex(position);
index = Mathf.Clamp(index, 0, RecyclerViewAdapter.GetItemCount() - 1);
float targetPosition = layoutManager.IndexToPosition(index);
UpdateCurrentIndex(index);
if (Mathf.Abs(targetPosition - position) <= 0.1f)
{
return false;
}
scroller.ScrollToDuration(targetPosition, 0.12f);
return true; return true;
} }
private void HandleScrollSettled()
{
if (snap && SnapToNearestItem())
{
return;
}
TryProcessPendingFocusRequest();
OnScrollStopped?.Invoke();
}
/// <summary> /// <summary>
/// 更新当前逻辑索引并在变化时触发事件通知<E9809A>? /// 更新当前逻辑索引并在变化时触发事件通知<E9809A>?
/// </summary> /// </summary>

View File

@ -55,8 +55,9 @@ namespace AlicizaX.UI
return delta * 0.1f; return delta * 0.1f;
} }
protected override void Elastic() protected override bool StartElasticMotion()
{ {
return false;
} }
} }
} }

View File

@ -5,6 +5,12 @@ namespace AlicizaX.UI
{ {
public class Scroller : MonoBehaviour, IScroller, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler public class Scroller : MonoBehaviour, IScroller, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler
{ {
private const float MaxInertiaVelocity = 60f;
private const float MinInertiaDuration = 0.06f;
private const float MaxInertiaDuration = 0.24f;
private const float MinInertiaDistanceFactor = 1.5f;
private const float MaxInertiaDistanceFactor = 6f;
protected enum MotionState protected enum MotionState
{ {
Idle, Idle,
@ -83,6 +89,8 @@ namespace AlicizaX.UI
private float motionDuration; private float motionDuration;
private float motionSpeed; private float motionSpeed;
private float inertiaVelocity; private float inertiaVelocity;
private float inertiaDistance;
private bool notifyMoveStoppedOnComplete;
public float MaxPosition => direction == Direction.Vertical ? Mathf.Max(contentSize.y - viewSize.y, 0) : Mathf.Max(contentSize.x - viewSize.x, 0); public float MaxPosition => direction == Direction.Vertical ? Mathf.Max(contentSize.y - viewSize.y, 0) : Mathf.Max(contentSize.x - viewSize.x, 0);
@ -154,7 +162,7 @@ namespace AlicizaX.UI
return; return;
} }
StartPositionMotion(position, scrollSpeed); StartPositionMotion(position, scrollSpeed, true);
} }
public virtual void ScrollToDuration(float position, float duration) public virtual void ScrollToDuration(float position, float duration)
@ -177,6 +185,7 @@ namespace AlicizaX.UI
motionTargetPosition = position; motionTargetPosition = position;
motionDuration = Mathf.Max(duration, 0.0001f); motionDuration = Mathf.Max(duration, 0.0001f);
motionElapsed = 0f; motionElapsed = 0f;
notifyMoveStoppedOnComplete = true;
} }
public virtual void ScrollToRatio(float ratio) public virtual void ScrollToRatio(float ratio)
@ -202,8 +211,7 @@ namespace AlicizaX.UI
return; return;
} }
Inertia(); StartReleaseMotion();
Elastic();
OnDragging?.Invoke(false); OnDragging?.Invoke(false);
} }
@ -234,8 +242,7 @@ namespace AlicizaX.UI
position += velocity; position += velocity;
OnValueChanged?.Invoke(position); OnValueChanged?.Invoke(position);
Inertia(); StartReleaseMotion();
Elastic();
} }
internal virtual float GetDelta(PointerEventData eventData) internal virtual float GetDelta(PointerEventData eventData)
@ -277,36 +284,57 @@ namespace AlicizaX.UI
motionState = MotionState.Inertia; motionState = MotionState.Inertia;
motionStartPosition = position; motionStartPosition = position;
motionElapsed = 0f; motionElapsed = 0f;
motionDuration = snap ? 0.1f : 1f; inertiaVelocity = Mathf.Clamp(velocity, -MaxInertiaVelocity, MaxInertiaVelocity);
inertiaVelocity = velocity > 0 ? Mathf.Min(velocity, 100) : Mathf.Max(velocity, -100); float normalizedVelocity = Mathf.Clamp01(Mathf.Abs(inertiaVelocity) / MaxInertiaVelocity);
motionDuration = Mathf.Lerp(MinInertiaDuration, MaxInertiaDuration, normalizedVelocity);
float distanceFactor = Mathf.Lerp(MinInertiaDistanceFactor, MaxInertiaDistanceFactor, normalizedVelocity);
inertiaDistance = inertiaVelocity * distanceFactor;
notifyMoveStoppedOnComplete = true;
} }
protected virtual void Elastic() protected virtual bool StartElasticMotion()
{ {
if (position < 0) if (position < 0)
{ {
StopMovement(); StopMovement();
StartPositionMotion(0, 7f); StartPositionMotion(0, 7f, true);
return true;
} }
else if (position > MaxPosition)
if (position > MaxPosition)
{ {
StopMovement(); StopMovement();
StartPositionMotion(MaxPosition, 7f); StartPositionMotion(MaxPosition, 7f, true);
return true;
} }
return false;
} }
protected void StopMovement() protected void StopMovement()
{ {
motionState = MotionState.Idle; motionState = MotionState.Idle;
notifyMoveStoppedOnComplete = false;
} }
private void StartPositionMotion(float targetPosition, float speed) private void StartReleaseMotion()
{
if (StartElasticMotion())
{
return;
}
Inertia();
}
private void StartPositionMotion(float targetPosition, float speed, bool notifyStopped)
{ {
motionState = MotionState.Smooth; motionState = MotionState.Smooth;
motionStartPosition = position; motionStartPosition = position;
motionTargetPosition = targetPosition; motionTargetPosition = targetPosition;
motionElapsed = Time.deltaTime; motionElapsed = Time.deltaTime;
motionSpeed = speed; motionSpeed = speed;
notifyMoveStoppedOnComplete = notifyStopped;
} }
private void TickSmooth(float deltaTime) private void TickSmooth(float deltaTime)
@ -343,8 +371,8 @@ namespace AlicizaX.UI
{ {
motionElapsed += deltaTime; motionElapsed += deltaTime;
float t = Mathf.Clamp01(motionElapsed / motionDuration); float t = Mathf.Clamp01(motionElapsed / motionDuration);
float y = (float)EaseUtil.EaseOutCirc(t) * 40f; float offset = (float)EaseUtil.EaseOutCirc(t) * inertiaDistance;
float nextPosition = motionStartPosition + y * inertiaVelocity; float nextPosition = motionStartPosition + offset;
float maxPosition = MaxPosition; float maxPosition = MaxPosition;
if (nextPosition < 0f) if (nextPosition < 0f)
@ -352,7 +380,7 @@ namespace AlicizaX.UI
position = 0f; position = 0f;
OnValueChanged?.Invoke(position); OnValueChanged?.Invoke(position);
StopMovement(); StopMovement();
StartPositionMotion(0f, 7f); StartPositionMotion(0f, 7f, true);
return; return;
} }
@ -361,7 +389,7 @@ namespace AlicizaX.UI
position = maxPosition; position = maxPosition;
OnValueChanged?.Invoke(position); OnValueChanged?.Invoke(position);
StopMovement(); StopMovement();
StartPositionMotion(maxPosition, 7f); StartPositionMotion(maxPosition, 7f, true);
return; return;
} }
@ -378,8 +406,10 @@ namespace AlicizaX.UI
{ {
motionState = MotionState.Idle; motionState = MotionState.Idle;
velocity = 0f; velocity = 0f;
bool shouldNotify = notifyStopped || notifyMoveStoppedOnComplete;
notifyMoveStoppedOnComplete = false;
if (notifyStopped) if (shouldNotify)
{ {
OnMoveStoped?.Invoke(); OnMoveStoped?.Invoke();
} }