fix
This commit is contained in:
parent
589da39636
commit
2177fd8334
@ -83,7 +83,8 @@ namespace AlicizaX.UI
|
||||
itemRender.Bind(data, index);
|
||||
}
|
||||
|
||||
itemRender.UpdateSelection(index == choiceIndex);
|
||||
bool selected = index == choiceIndex;
|
||||
itemRender.SyncSelection(selected);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -98,7 +99,6 @@ namespace AlicizaX.UI
|
||||
|
||||
if (TryGetItemRender(viewHolder, out var itemRender))
|
||||
{
|
||||
itemRender.UpdateSelection(false);
|
||||
itemRender.Unbind();
|
||||
}
|
||||
}
|
||||
@ -373,6 +373,7 @@ namespace AlicizaX.UI
|
||||
}
|
||||
|
||||
if (index == choiceIndex) return;
|
||||
int previousChoice = choiceIndex;
|
||||
|
||||
if (choiceIndex != -1 && TryGetViewHolder(choiceIndex, out var oldHolder))
|
||||
{
|
||||
|
||||
@ -24,6 +24,8 @@ namespace AlicizaX.UI
|
||||
/// <param name="selected">是否处于选中状态。</param>
|
||||
void UpdateSelection(bool selected);
|
||||
|
||||
void SyncSelection(bool selected);
|
||||
|
||||
/// <summary>
|
||||
/// 清理当前渲染实例上的绑定状态。
|
||||
/// </summary>
|
||||
@ -76,6 +78,8 @@ namespace AlicizaX.UI
|
||||
/// <param name="selected">是否处于选中状态。</param>
|
||||
internal abstract void UpdateSelectionInternal(bool selected);
|
||||
|
||||
internal abstract void SyncSelectionInternal(bool selected);
|
||||
|
||||
/// <summary>
|
||||
/// 清理当前绑定产生的临时状态。
|
||||
/// </summary>
|
||||
@ -100,6 +104,11 @@ namespace AlicizaX.UI
|
||||
UpdateSelectionInternal(selected);
|
||||
}
|
||||
|
||||
void IItemRender.SyncSelection(bool selected)
|
||||
{
|
||||
SyncSelectionInternal(selected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 由框架内部调用,清理当前渲染实例的绑定状态。
|
||||
/// </summary>
|
||||
@ -229,9 +238,12 @@ namespace AlicizaX.UI
|
||||
/// <param name="selected">是否处于选中状态。</param>
|
||||
internal override void UpdateSelectionInternal(bool selected)
|
||||
{
|
||||
EnsureHolder();
|
||||
IsSelected = selected;
|
||||
OnSelectionChanged(selected);
|
||||
SetSelectionState(selected, true);
|
||||
}
|
||||
|
||||
internal override void SyncSelectionInternal(bool selected)
|
||||
{
|
||||
SetSelectionState(selected, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -241,12 +253,7 @@ namespace AlicizaX.UI
|
||||
{
|
||||
if (Holder != null)
|
||||
{
|
||||
if (IsSelected)
|
||||
{
|
||||
IsSelected = false;
|
||||
OnSelectionChanged(false);
|
||||
}
|
||||
|
||||
ClearSelectionState();
|
||||
OnClear();
|
||||
if (interactionProxy != null)
|
||||
{
|
||||
@ -262,7 +269,6 @@ namespace AlicizaX.UI
|
||||
CurrentIndex = -1;
|
||||
CurrentLayoutIndex = -1;
|
||||
CurrentBindingVersion = 0;
|
||||
IsSelected = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -295,6 +301,33 @@ namespace AlicizaX.UI
|
||||
OnBind(itemData, index);
|
||||
}
|
||||
|
||||
private void SetSelectionState(bool selected, bool notify)
|
||||
{
|
||||
EnsureHolder();
|
||||
bool previousSelected = IsSelected;
|
||||
if (previousSelected == selected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsSelected = selected;
|
||||
if (notify)
|
||||
{
|
||||
OnSelectionChanged(IsSelected);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearSelectionState()
|
||||
{
|
||||
if (!IsSelected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsSelected = false;
|
||||
OnSelectionChanged(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 每次当前持有者绑定到新的数据项时调用。
|
||||
/// 仅用于执行数据驱动的界面刷新,例如文本、图片与状态更新。
|
||||
|
||||
@ -62,6 +62,18 @@ namespace AlicizaX.UI
|
||||
return focusAnchor;
|
||||
}
|
||||
|
||||
internal bool TryGetFocusTarget(out GameObject target)
|
||||
{
|
||||
target = null;
|
||||
if (!TryGetFocusableSelectable(out Selectable selectable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
target = selectable.gameObject;
|
||||
return target != null;
|
||||
}
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
if ((flags & ItemInteractionFlags.PointerClick) != 0)
|
||||
@ -181,6 +193,42 @@ namespace AlicizaX.UI
|
||||
focusAnchor = ownedSelectable;
|
||||
}
|
||||
|
||||
private bool TryGetFocusableSelectable(out Selectable selectable)
|
||||
{
|
||||
EnsureFocusAnchor();
|
||||
if (IsSelectableFocusable(focusAnchor))
|
||||
{
|
||||
selectable = focusAnchor;
|
||||
return true;
|
||||
}
|
||||
|
||||
Selectable[] selectables = GetComponentsInChildren<Selectable>(true);
|
||||
for (int i = 0; i < selectables.Length; i++)
|
||||
{
|
||||
Selectable candidate = selectables[i];
|
||||
if (candidate == focusAnchor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsSelectableFocusable(candidate))
|
||||
{
|
||||
selectable = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
selectable = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsSelectableFocusable(Selectable selectable)
|
||||
{
|
||||
return selectable != null &&
|
||||
selectable.IsActive() &&
|
||||
selectable.IsInteractable();
|
||||
}
|
||||
|
||||
private static bool RequiresSelection(ItemInteractionFlags interactionFlags)
|
||||
{
|
||||
const ItemInteractionFlags selectionFlags =
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace AlicizaX.UI
|
||||
@ -12,5 +13,9 @@ namespace AlicizaX.UI
|
||||
disabledNavigation.mode = Navigation.Mode.None;
|
||||
navigation = disabledNavigation;
|
||||
}
|
||||
|
||||
public override void OnMove(AxisEventData eventData)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,23 +36,37 @@ namespace AlicizaX.UI
|
||||
|
||||
bool isLoopSource = itemCount != realCount;
|
||||
bool allowWrap = isLoopSource && options.Wrap;
|
||||
int nextLayoutIndex = currentHolder.Index + step;
|
||||
if (allowWrap)
|
||||
{
|
||||
nextLayoutIndex = WrapIndex(nextLayoutIndex, realCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
nextLayoutIndex = Mathf.Clamp(nextLayoutIndex, 0, itemCount - 1);
|
||||
}
|
||||
|
||||
if (!allowWrap && nextLayoutIndex == currentHolder.Index)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int originalIndex = currentHolder.Index;
|
||||
int currentIndex = currentHolder.Index;
|
||||
int nextLayoutIndex = currentIndex;
|
||||
int maxAttempts = allowWrap ? realCount : itemCount;
|
||||
ScrollAlignment alignment = ResolveAlignment(direction, options.Alignment);
|
||||
return recyclerView.TryFocusIndex(nextLayoutIndex, options.SmoothScroll, 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;
|
||||
}
|
||||
|
||||
private int GetStep(MoveDirection direction)
|
||||
|
||||
@ -686,7 +686,10 @@ namespace AlicizaX.UI
|
||||
/// </summary>
|
||||
/// <param name="entryDirection">焦点进入列表时的方向。</param>
|
||||
/// <returns>成功聚焦某个列表项时返回 <see langword="true"/>;否则返回 <see langword="false"/>。</returns>
|
||||
public bool TryFocusEntry(MoveDirection entryDirection)
|
||||
public bool TryFocusEntry(
|
||||
MoveDirection entryDirection,
|
||||
bool smooth = false,
|
||||
ScrollAlignment alignment = ScrollAlignment.Center)
|
||||
{
|
||||
if (RecyclerViewAdapter == null)
|
||||
{
|
||||
@ -710,7 +713,8 @@ namespace AlicizaX.UI
|
||||
: Mathf.Clamp(CurrentIndex, 0, realCount - 1);
|
||||
}
|
||||
|
||||
return TryFocusIndex(targetIndex);
|
||||
int step = entryDirection is MoveDirection.Up or MoveDirection.Left ? -1 : 1;
|
||||
return TryFocusIndexRange(targetIndex, step, realCount, smooth, alignment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -728,14 +732,40 @@ namespace AlicizaX.UI
|
||||
}
|
||||
|
||||
ItemInteractionProxy proxy = holder.GetComponent<ItemInteractionProxy>();
|
||||
Selectable selectable = proxy != null ? proxy.GetSelectable() : holder.GetComponent<Selectable>();
|
||||
if (selectable == null)
|
||||
if (proxy != null)
|
||||
{
|
||||
selectable = holder.GetComponentInChildren<Selectable>(true);
|
||||
return proxy.TryGetFocusTarget(out target);
|
||||
}
|
||||
|
||||
target = selectable != null ? selectable.gameObject : holder.gameObject;
|
||||
return target != null;
|
||||
Selectable selectable = holder.GetComponent<Selectable>();
|
||||
if (!IsSelectableFocusable(selectable))
|
||||
{
|
||||
Selectable[] selectables = holder.GetComponentsInChildren<Selectable>(true);
|
||||
for (int i = 0; i < selectables.Length; i++)
|
||||
{
|
||||
if (IsSelectableFocusable(selectables[i]))
|
||||
{
|
||||
selectable = selectables[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsSelectableFocusable(selectable))
|
||||
{
|
||||
target = selectable.gameObject;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ExecuteEvents.CanHandleEvent<IMoveHandler>(holder.gameObject) ||
|
||||
ExecuteEvents.CanHandleEvent<ISelectHandler>(holder.gameObject) ||
|
||||
ExecuteEvents.CanHandleEvent<ISubmitHandler>(holder.gameObject))
|
||||
{
|
||||
target = holder.gameObject;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -795,11 +825,24 @@ namespace AlicizaX.UI
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ReferenceEquals(eventSystem.currentSelectedGameObject, target))
|
||||
if (ReferenceEquals(eventSystem.currentSelectedGameObject, target))
|
||||
{
|
||||
eventSystem.SetSelectedGameObject(target);
|
||||
ScheduleFocusRecovery(target);
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventSystem.alreadySelecting)
|
||||
{
|
||||
ScheduleFocusRecovery(target);
|
||||
return;
|
||||
}
|
||||
|
||||
eventSystem.SetSelectedGameObject(target);
|
||||
ScheduleFocusRecovery(target);
|
||||
}
|
||||
|
||||
private void ScheduleFocusRecovery(GameObject target)
|
||||
{
|
||||
if (focusRecoveryCoroutine != null)
|
||||
{
|
||||
StopCoroutine(focusRecoveryCoroutine);
|
||||
@ -808,6 +851,34 @@ namespace AlicizaX.UI
|
||||
focusRecoveryCoroutine = StartCoroutine(RecoverFocusNextFrame(target));
|
||||
}
|
||||
|
||||
private bool TryFocusIndexRange(int startIndex, int step, int itemCount, bool smooth, ScrollAlignment alignment)
|
||||
{
|
||||
if (itemCount <= 0 || step == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = Mathf.Clamp(startIndex, 0, itemCount - 1);
|
||||
while (index >= 0 && index < itemCount)
|
||||
{
|
||||
if (TryFocusIndex(index, smooth, alignment))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
index += step;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsSelectableFocusable(Selectable selectable)
|
||||
{
|
||||
return selectable != null &&
|
||||
selectable.IsActive() &&
|
||||
selectable.IsInteractable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在下一帧尝试恢复目标对象的焦点,避免布局刷新期间焦点丢失。
|
||||
/// </summary>
|
||||
|
||||
@ -195,9 +195,12 @@ namespace AlicizaX.UI
|
||||
return index >= 0 && TryFocus(index, smooth, alignment);
|
||||
}
|
||||
|
||||
public bool TryFocusEntry(MoveDirection entryDirection)
|
||||
public bool TryFocusEntry(
|
||||
MoveDirection entryDirection,
|
||||
bool smooth = false,
|
||||
ScrollAlignment alignment = ScrollAlignment.Center)
|
||||
{
|
||||
return _recyclerView != null && _recyclerView.TryFocusEntry(entryDirection);
|
||||
return _recyclerView != null && _recyclerView.TryFocusEntry(entryDirection, smooth, alignment);
|
||||
}
|
||||
|
||||
public void ScrollToIndex(int index, bool smooth = false)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user