com.alicizax.unity.ui.exten.../Runtime/RecyclerView/Navigation/RecyclerNavigationController.cs
2026-04-13 15:45:34 +08:00

116 lines
3.8 KiB
C#

using UnityEngine;
using UnityEngine.EventSystems;
namespace AlicizaX.UI
{
public sealed class RecyclerNavigationController
{
private readonly RecyclerView recyclerView;
public RecyclerNavigationController(RecyclerView recyclerView)
{
this.recyclerView = recyclerView;
}
public bool TryMove(ViewHolder currentHolder, MoveDirection direction, RecyclerNavigationOptions options)
{
#if !UX_NAVIGATION
return false;
#else
if (recyclerView == null ||
recyclerView.RecyclerViewAdapter == null ||
currentHolder == null)
{
return false;
}
int itemCount = recyclerView.RecyclerViewAdapter.GetItemCount();
int realCount = recyclerView.RecyclerViewAdapter.GetRealCount();
if (itemCount <= 0 || realCount <= 0)
{
return false;
}
int step = GetStep(direction);
if (step == 0)
{
return false;
}
bool isLoopSource = itemCount != realCount;
bool allowWrap = isLoopSource && options.Wrap;
int originalIndex = currentHolder.Index;
int currentIndex = currentHolder.Index;
int nextLayoutIndex = currentIndex;
int maxAttempts = allowWrap ? realCount : itemCount;
ScrollAlignment alignment = ResolveAlignment(direction, options.Alignment);
for (int attempt = 0; attempt < maxAttempts; attempt++)
{
nextLayoutIndex += step;
if (allowWrap)
{
nextLayoutIndex = WrapIndex(nextLayoutIndex, realCount);
}
else
{
nextLayoutIndex = Mathf.Clamp(nextLayoutIndex, 0, itemCount - 1);
if (nextLayoutIndex == currentIndex)
{
break;
}
}
if (recyclerView.TryFocusIndex(nextLayoutIndex, options.SmoothScroll, alignment))
{
return true;
}
currentIndex = nextLayoutIndex;
}
recyclerView.TryFocusIndex(originalIndex, false, alignment);
return false;
#endif
}
private int GetStep(MoveDirection direction)
{
int unit = Mathf.Max(1, recyclerView.LayoutManager != null ? recyclerView.LayoutManager.Unit : 1);
bool vertical = recyclerView.Direction == Direction.Vertical;
return direction switch
{
MoveDirection.Left => vertical ? -1 : -unit,
MoveDirection.Right => vertical ? 1 : unit,
MoveDirection.Up => vertical ? -unit : -1,
MoveDirection.Down => vertical ? unit : 1,
_ => 0
};
}
private static int WrapIndex(int index, int count)
{
int wrapped = index % count;
return wrapped < 0 ? wrapped + count : wrapped;
}
private ScrollAlignment ResolveAlignment(MoveDirection direction, ScrollAlignment fallback)
{
if (recyclerView == null || recyclerView.LayoutManager == null)
{
return fallback;
}
bool vertical = recyclerView.Direction == Direction.Vertical;
return direction switch
{
MoveDirection.Down when vertical => ScrollAlignment.End,
MoveDirection.Up when vertical => ScrollAlignment.Start,
MoveDirection.Right when !vertical => ScrollAlignment.End,
MoveDirection.Left when !vertical => ScrollAlignment.Start,
_ => fallback
};
}
}
}